防止函数(或装饰器)嵌套

时间:2013-06-03 20:20:55

标签: python

我在装饰器中有一些代码,我只想运行一次。许多其他函数(实用程序和其他函数)将在后面调用,我想确保可能具有此装饰器的其他函数不会在函数调用的嵌套中意外使用。

我还希望能够随时检查当前代码是否已包装在装饰器中。

我写过这篇文章,但我只想看看是否有人能想到一个更好/更优雅的解决方案,而不是检查堆栈中的(希望!)唯一函数名称。

import inspect

def my_special_wrapper(fn):
    def my_special_wrapper(*args, **kwargs):
        """ Do some magic, only once! """
        # Check we've not done this before
        for frame in inspect.stack()[1:]:  # get stack, ignoring current!
            if frame[3] == 'my_special_wrapper':
                raise StandardError('Special wrapper cannot be nested')
        # Do magic then call fn
        # ...
        fn(*args, **kwargs)
    return my_special_wrapper

def within_special_wrapper():
    """ Helper to check that the function has been specially wrapped """
    for frame in inspect.stack():
        if frame[3] == 'my_special_wrapper':
            return True
    return False

@my_special_wrapper
def foo():
    print within_special_wrapper()
    bar()
    print 'Success!'

@my_special_wrapper    
def bar():
    pass

foo()

3 个答案:

答案 0 :(得分:3)

以下是使用全局执行此任务的示例 - 我认为这是一种相对安全的方式:

from contextlib import contextmanager
from functools import wraps

_within_special_context = False

@contextmanager
def flag():
    global _within_special_context
    _within_special_context = True
    try:
        yield
    finally:
        _within_special_context = False


#I'd argue this would be best replaced by just checking the variable, but
#included for completeness.
def within_special_wrapper():
    return _within_special_context


def my_special_wrapper(f):
    @wraps(f)
    def internal(*args, **kwargs):
        if not _within_special_context:
            with flag():
                ...
                f(*args, **kwargs)
        else:
            raise Exception("No nested calls!")
    return internal

@my_special_wrapper
def foo():
    print(within_special_wrapper())
    bar()
    print('Success!')

@my_special_wrapper
def bar():
    pass

foo()

结果是:

True
Traceback (most recent call last):
  File "/Users/gareth/Development/so/test.py", line 39, in <module>
    foo()
  File "/Users/gareth/Development/so/test.py", line 24, in internal
    f(*args, **kwargs)
  File "/Users/gareth/Development/so/test.py", line 32, in foo
    bar()
  File "/Users/gareth/Development/so/test.py", line 26, in internal
    raise Exception("No nested calls!")
Exception: No nested calls!

使用上下文管理器可确保取消设置变量。您可以使用try/finally,但如果您想修改不同情况下的行为,可以使上下文管理器具有灵活性和可重用性。

答案 1 :(得分:2)

显而易见的解决方案是让special_wrapper设置一个全局标志,如果设置了标志,就跳过它的魔力。

这是关于全局变量的唯一好用 - 允许单段代码存储仅在该代码中使用的信息,但需要在该代码的执行生存期内存活。

不需要在全局范围内设置。例如,该函数可以在自身上设置标志,或者在任何对象或类上设置标志,只要没有别的东西可以触及它。

正如Lattyware在评论中所指出的那样,您将要使用try / except或者甚至更好的上下文管理器来确保该变量未被设置。

更新:如果您需要包装的代码能够检查它是否被包装,那么提供一个返回该标志值的函数。你可能想用一个整齐的类来包装它。

更新2:我看到你正在为事务管理这样做。可能已经有图书馆这样做了。我强烈建议您至少查看他们的代码。

答案 2 :(得分:0)

虽然我的解决方案在技术上有效,但它需要手动重置装饰器,但你可以很好地修改一些东西,使得最外面的函数是一个类(实例是在{中传递给它的装饰函数的包装器) {1}}),并在__init__中调用reset(),然后允许您使用__exit__()语句创建装饰器,以便在上下文中仅使用一次。另请注意,由于with关键字,它需要Python 3,但可以使用dict代替flags变量轻松地将其调整为2.7。

nonlocal

测试:

def once_usable(decorator):
    "Apply this decorator function to the decorator you want to be usable only once until it is reset."

    def outer_wrapper():
        flag = False

        def inner_wrapper(*args, **kwargs):
            nonlocal flag
            if not flag:
                flag = True
                return decorator(*args, **kwargs)
            else:
                print("Decorator currently unusable.") # raising an Error also works

        def decorator_reset():
            nonlocal flag
            flag = False

        return (inner_wrapper, decorator_reset)

    return outer_wrapper()