我可以使用函数结果的属性作为装饰器吗?

时间:2012-03-23 19:44:26

标签: python

我在python中创建了一个简单的事件系统,我发现每次触发事件的方式几乎都是相同的:无论是在通话结束时,还是在通话结束之前。感觉这对装饰工作者来说是件好事。这是我正在使用的代码:

from functools import wraps

def fires(event):
    """
    Returns a decorater that causes an `Event` to fire immediately before the
    decorated function is called
    """
    def beforeDecorator(f):
        """Fires the event before the function executes"""
        @wraps(f)
        def wrapped(*args, **kargs):
            event.fire(*args, **kargs)
            return f(*args, **kargs)
        return wrapped

    def afterDecorator(f):
        """Fires the event after the function executes"""
        @wraps(f)
        def wrapped(*args, **kargs):
            result = f(*args, **kargs)
            event.fire(*args, **kargs)
            return result
        return wrapped

    # Should allow more explicit `@fires(event).uponCompletion` and
    # `@fires(event).whenCalled`
    afterDecorator.onceComplete = afterDecorator
    afterDecorator.whenCalled = afterDecorator

    return afterDecorator

使用此代码,我可以成功写出:

@fires(myEvent)
def foo(y):
    return y*y

print func(2)

一切正常。当我尝试写这个问题时,问题出现了:

@fires(myEvent).onceComplete
def foo(y):
    return y*y

print func(2)

这给了我一个语法错误。复杂装饰器有一些特殊的语法吗?解析器在第一组括号后停止吗?

3 个答案:

答案 0 :(得分:3)

根据grammar specification,不可能:

funcdef        ::=  [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
decorators     ::=  decorator+
decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
dotted_name    ::=  identifier ("." identifier)*
parameter_list ::=  (defparameter ",")*
                    (  "*" [parameter] ("," defparameter)*
                    [, "**" parameter]
                    | "**" parameter
                    | defparameter [","] )
parameter      ::=  identifier [":" expression]
defparameter   ::=  parameter ["=" expression]
funcname       ::=  identifier

装饰者必须在末尾加上括号

答案 1 :(得分:2)

我添加了前后变量的预计算(由于调用技巧,所有闭包都是在导入时创建的,只是在应用装饰器时使用),使选择依赖于可选参数。 meta-decorator,并放入一个try / finally块,以确保您的事后总是触发。通过这种方法,功能属性的问题变得没有实际意义。

invoke = lambda f: f()  # trick used in JavaScript frameworks all the time

@invoke  # closure becomes fires
def fires():
    def beforeDecorator(f, event):
        """Fires the event before the function executes"""
        @wraps(f)
        def wrapped(*args, **kargs):
            event.fire(*args, **kargs)
            return f(*args, **kargs)
        return wrapped

    def afterDecorator(f, event):
        """Fires the event after the function executes"""
        @wraps(f)
        def wrapped(*args, **kargs):
            try:
              result = f(*args, **kargs)
            finally:
              event.fire(*args, **kargs)
            return result
        return wrapped

    def closure(event, after=False):  # becomes fires
      def decorator(function):
        if after:
            return afterDecorator(function, event)
        else:
            return beforeDecorator(function, event)
      return decorator
    return closure

答案 2 :(得分:1)

我不确定是否有办法获得你想要的语法,但这里有另一种选择。

只需在fires()装饰器中添加一个额外的参数,以确定它是否应该在之前或之后发生:

def fires(event, before=True):
    """
    Returns a decorater that causes an `Event` to fire immediately before or
    after the decorated function is called
    """
    if before:
        def decorator(f):
            """Fires the event before the function executes"""
            @wraps(f)
            def wrapped(*args, **kargs):
                event.fire(*args, **kargs)
                return f(*args, **kargs)
            return wrapped
    else:
        def decorator(f):
            """Fires the event after the function executes"""
            @wraps(f)
            def wrapped(*args, **kargs):
                result = f(*args, **kargs)
                event.fire(*args, **kargs)
                return result
            return wrapped

    return decorator

然后像这样使用它:

@fires(myEvent, before=False)   # or before=True, defaults to True
def foo(y):
    return y*y