【问题标题】:Python: shortcut for writing decorators which accept arguments?Python:编写接受参数的装饰器的快捷方式?
【发布时间】:2012-05-23 13:26:50
【问题描述】:

Python 标准库是否有编写接受参数的装饰器的快捷方式?

例如,如果我想写一个像with_timeout(timeout)这样的装饰器:

@with_timeout(10.0)
def cook_eggs(eggs):
    while not eggs.are_done():
        eggs.cook()

我必须写这样的东西:

def with_timeout(timeout):
    _func = [None]
    def with_timeout_helper(*args, **kwargs):
        with Timeout(timeout):
            return _func[0](*args, **kwargs)
    def with_timeout_return(f):
        return functools.wraps(f)(with_timeout_helper)
    return with_timeout_return

但这太冗长了。有没有捷径可以让接受参数的装饰器更容易编写?

注意:我意识到也可以使用三个嵌套函数来实现带参数的装饰器……但这也有点不太理想。

例如,可能类似于@decorator_with_arguments 函数:

@decorator_with_arguments
def timeout(f, timeout):
    @functools.wraps(f)
    def timeout_helper(*args, **kwargs):
        with Timeout(timeout):
            return f(*args, **kwargs)
    return timeout_helper

【问题讨论】:

标签: python decorator


【解决方案1】:

另一种方法,不使用 lambda:

def decorator_with_arguments(f):
    @functools.wraps(f)
    def with_arguments_helper(*args, **kwargs):
        def decorator(g):
            return f(g, *args, **kwargs)
        return decorator
    return with_arguments_helper

【讨论】:

    【解决方案2】:

    根据 Jakob 的建议,我实现了一个小的 Decorator 类,我觉得它做得相当不错:

    class Decorator(object):
        def __call__(self, f):
            self.f = f
            return functools.wraps(f)(lambda *a, **kw: self.wrap(*a, **kw))
    
        def wrap(self, *args, **kwrags):
            raise NotImplemented("Subclasses of Decorator must implement 'wrap'")
    
    class with_timeout(Decorator):
        def __init__(self, timeout):
            self.timeout = timeout
    
        def wrap(self, *args, **kwargs):
            with Timeout(timeout):
                return self.f(*args, **kwargs)
    

    【讨论】:

    • 我修正了你的 with_timeout 方法 warp 的错字
    • 天啊!谢谢。这可能是我今晚第 8 次输入“warp”而不是“wrap”。而且我没有多次输入“wrap”。
    【解决方案3】:

    首先,我们可以定义一个小的元装饰器:

    def decorator_with_arguments(wrapper):
        return lambda *args, **kwargs: lambda func: wrapper(func, *args, **kwargs)
    

    这允许我们创建接受如下参数的装饰器:

    @decorator_with_arguments
    def my_wrapper(func, *decorator_args, **decorator_kwargs):
        def wrapped(*call_args, **call_kwargs):
            print "from decorator:", decorator_args, decorator_kwargs
            func(*call_args, **call_kwargs)
        return wrapped
    

    然后就可以正常使用了:

    @my_wrapper(1, 2, 3)
    def test(*args, **kwargs):
        print "passed directly:", args, kwargs
    
    test(4, 5, 6)
    

    添加functools.wraps 装饰留作练习:)

    【讨论】:

      【解决方案4】:

      我知道你说它感觉不太理想,但我仍然觉得使用三个嵌套模型是最干净的解决方案。内部的两个函数只是为接受参数的函数定义装饰器的“正常”方式(请参阅 python 的 @wraps 文档中的 example)。外层实际上只是一个接受和参数并返回装饰器的函数。

      def with_timeout(timeout):
          def decorator(f):
              @wraps(f)
              def wrapper(*args, **kwargs):
                  with Timeout(timeout):
                      return f(*args, **kwargs)
              return wrapper
          return decorator
      

      【讨论】:

      • 抱歉,我不同意。三个嵌套函数(或者,就此而言,我这样做的方式)一点也不干净。这是一个黑客。更好的是,例如,@decorator_with_arguments 装饰器(请参阅我更新的问题)。
      【解决方案5】:

      老实说,我倾向于将装饰器写成类

      class TestWithArgs(object):
          def __init__(self, *deco_args, **deco_kwargs):
              self.deco_args = deco_args
              self.deco_kwargs = deco_kwargs
          def __call__(self, func):
              def _wrap(self, *args, **kwargs):
                  print "Blah blah blah"
                  return func(*args, **kwargs)
              return _wrap
      

      如果不是稍微清晰一点,就什么都不是

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-02
        • 1970-01-01
        • 1970-01-01
        • 2015-09-03
        • 2017-01-13
        • 1970-01-01
        • 2020-06-20
        • 1970-01-01
        相关资源
        最近更新 更多