4

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

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

我已经写了这个,但我只是想看看其他人是否能想到比检查堆栈中的(希望!)唯一函数名更好/更优雅的解决方案。

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()
4

3 回答 3

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,但如果您想针对不同情况修改行为,可以使上下文管理器变得灵活且可重用。

于 2013-06-03T21:01:41.053 回答
2

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

这是对全局变量的唯一良好用途——允许一段代码存储仅在该代码中使用的信息,但这些信息需要在该代码的执行生命周期内继续存在。

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

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

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

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

于 2013-06-03T20:25:51.797 回答
1

虽然我的解决方案在技术上有效,但它需要手动重置装饰器,但您可以很好地修改一些东西,使最外面的函数改为一个类(实例是传递给它的装饰函数的包装器__init__),并且有reset()被调用__exit__(),这将允许您使用该with语句来创建装饰器,使其在上下文中只能使用一次。另请注意,由于nonlocal关键字,它需要 Python 3,但可以很容易地适应 2.7,用 dict 代替 flag 变量。

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()

测试:

>>> def a(aa):
    return aa*2

>>> def b(bb):
    def wrapper(*args, **kwargs):
        print("Decorated.")
        return bb(*args, **kwargs)

    return wrapper

>>> dec, reset = once_usable(b)
>>> aa = dec(a)
>>> aa(22)
Decorated.
44
>>> aaa = dec(a)
Decorator currently unusable.
>>> reset()
>>> aaa = dec(a)
>>> aaa(11)
Decorated.
22
于 2013-06-03T20:56:32.817 回答