2012-03-23 65 views
4

我已經使自己成爲一個簡單的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) 

這給我一個語法錯誤。是否有一些複雜裝飾器的特殊語法?解析器在第一組括號後停止嗎?

+0

不只是任何表達式都可以用作裝飾器。例如'@(lambda f:f())'不起作用。 – wberry 2012-03-23 19:59:06

回答

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 ust在末尾有圓括號

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

當然,由於'invoke(fires)'被調用,它會擴展爲'fires()',因此會失敗,因爲'event'參數缺失,所以會調用原始'fires'而不帶參數。 – Eric 2012-03-23 19:57:27

+1

「@ invoke」裝飾器使內部函數「closure」替換原來的「fires」。所以一旦定義了它,你就可以用'@fires(myevent,True)'來裝飾事物,並且它會在之後觸發。 – wberry 2012-03-23 20:02:58

+0

啊,現在你已經移動了參數,這是有道理的。你確定這是一個改進嗎? @invoke看起來像一個黑客。感覺就像我應該使用一個類來包裝這兩個版本。 – Eric 2012-03-23 20:05:41

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 2012-03-23 19:58:34

相關問題