【问题标题】:Statement decorators声明装饰器
【发布时间】:2015-01-30 22:07:12
【问题描述】:

我们有一些看起来像这样的代码:

from third_party_library import foo

for n in range(3):
    try:
        foo(args)
        break
    except:
        print "Retry %i / 3" % n

我想用一个装饰器,让我们的代码更简洁,看起来像这样:

from third_party_library import foo

@retry(3)
foo(args)

这会产生语法错误。我是否遗漏了什么,或者 python 只是不允许在语句上使用装饰器?

【问题讨论】:

    标签: python function decorator


    【解决方案1】:

    装饰器只能应用于函数和类定义,例如:

    @decorator
    def func():
        ...
    
    @decorator
    class MyClass(object):
        ...
    

    您不能在该语言的其他任何地方使用它们。


    要做你想做的事,你可以创建一个普通的retry 函数并将fooargs 作为参数传递。实现看起来像这样:

    def retry(times, func, *args, **kwargs):
        for n in xrange(times):
            try:
                func(*args, **kwargs)
                break
            except Exception:  # Try to catch something more specific
                print "Retry %i / %i" % (n, times)
    

    【讨论】:

    • 3 应该排在第一位,这样*args(和**kwargs)可以用于支持具有多个参数的函数。否则正是我要建议的。
    • 您可以使用内联函数装饰器。例如,如果 OP 确实有一个假设的装饰 retry(),他们可以在他们的 for 循环中调用 retry(3)(foo)()
    【解决方案2】:

    Python 不允许在语句上使用装饰器;它们只允许在类和函数定义中使用。你可以在the grammar specification 的顶部附近看到这个。

    【讨论】:

      【解决方案3】:

      装饰器是在 Python 2.4 中引入的。最初,它们仅支持函数和方法声明 (PEP 318)。

      Python 3 将它们扩展为类声明 (PEP 3129)。

      任何其他语言结构都不支持装饰器。

      【讨论】:

        【解决方案4】:

        装饰器确实不能应用于语句,您可以按照另一个答案中的描述执行重试功能。然而,在我看来,还有一种更 Pythonesque 的方法,即使用上下文管理器。让我以可运行的方式重新表述您的问题,并让代码说话:

        import random
        def foo():
            """The function you were importing, now runnable"""
            n = random.randint(1,10)
            print "Got", n,
            if n < 8:
                raise Exception("Failed")
        
        for n in range(3):
            try:
                foo()
                break
            except:
                print "Retry %i / 3" % n
        

        使用(自动生成的)ContextManager,代码将如下所示:

        import random
        def foo():
            """The function you were importing, now runnable"""
            n = random.randint(1,10) 
            print "Got", n, 
            if n < 8:
                raise Exception("Failed")
        
        from contextlib import contextmanager
        @contextmanager
        def retry():
            """Something similar to a decorator for statements"""
                try:
                    yield
                except:
                    print "Retry %i / 3" % n
        
        for n in range(3):
            with retry():
                foo()
                break
        

        如您所见,解决方案并不完美,因为 ContextManager 可以只执行一次语句(在 yield 时执行)而不是循环执行。然而,至少对于这个简单的例子,我似乎更清楚,即使循环必须在外面完成。详情见https://docs.python.org/2.7/library/contextlib.htmlhttps://docs.python.org/2/reference/datamodel.html#context-managers

        【讨论】:

          【解决方案5】:

          装饰器本身就是包装函数(以参数为函数)并返回新函数的函数;

          所以你可以像这样内联使用它:

          retry(3)(foo)(args)
          #retry(3) is function accept function
          #retry(3)(foo) return the new function 
          #then u exploit the function by call with the `args`
          

          【讨论】:

            猜你喜欢
            • 2016-11-26
            • 1970-01-01
            • 1970-01-01
            • 2021-01-04
            • 1970-01-01
            • 2019-10-02
            • 1970-01-01
            • 2023-01-28
            • 2019-08-07
            相关资源
            最近更新 更多