2012-03-23 13 views
4

私は自分自身をPythonのシンプルなイベントシステムにしました。イベントの発生方法は毎回ほぼ同じでした。コール終了時か、その前のどちらかでした。これはデコレータとして持っているのがいいことだと感じました。ここで私が使用しているコードです:関数の結果のプロパティをデコレータとして使用できますか?

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

print func(2) 

を、すべてが動作します:

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).onceComplete 
def foo(y): 
    return y*y 

print func(2) 

これは私に構文エラーをもたらします。複雑なデコレータの特殊な構文はありますか?パーサーは、最初の括弧の後に停止しますか?

+0

デコレータとしてだけでなく、例えば ​​'@(lambda f:f())'は動作しません。 – wberry

答えて

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 

デコレータm最後にかっこを入れてください

2

前と後のバリアントの事前計算を追加しました(呼び出しトリックによってすべてのクロージャがインポート時に作成され、単にデコレータが適用されるたびに使用されます)。あなたのアフターイベントが常に発火することを確認するために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 
+0

これは引数なしで元の 'fires'を呼び出します。' invoke(fires) 'が呼び出されます。これは' fires() 'に展開され、' event'引数がないためエラーになります。 – Eric

+1

'@ invoke'デコレータは、内部関数' closure'を元の 'fires'に置き換えます。一度定義されると '@fires(myevent、True)'で物を飾ることができます。 – wberry

+0

ああ、あなたが議論を動かしたのは今や意味があります。あなたはそれが改善だと確信していますか? @invokeはハックのように見えます。私は両方のバージョンをラップするためにクラスを使用する必要があるように感じる。 – Eric

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 
+0

ええ、私はこれをやっていると思っていましたが、それは他の方法でよりきれいに感じます。このようにしなければならない場合は、おそらく '@ fires.whenCalled(myEvent)' – Eric

関連する問題