2012-03-23 2 views
4

Ich habe mir ein einfaches Event-System in Python gemacht, und ich fand, dass die Art und Weise, wie ich die Ereignisse abfeuerte, jedes Mal ziemlich gleich war: entweder am Ende eines Anrufs oder davor. Es fühlte sich an, als wäre es eine schöne Sache, als Dekorateur zu haben. Hier ist der Code Ich verwende:Kann ich eine Eigenschaft des Ergebnisses einer Funktion als Dekorator verwenden?

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 

Mit diesem Code kann ich dies erfolgreich schreiben:

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

print func(2) 

Und alles funktioniert. Das Problem kommt, wenn ich versuche, dies zu schreiben:

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

print func(2) 

Das gibt mir einen Syntaxfehler. Gibt es eine spezielle Syntax für komplexe Dekoratoren? Stoppt der Parser nach der ersten Klammer?

+0

Nicht nur jeder Ausdruck kann als Dekorateur verwendet werden. Zum Beispiel funktioniert '@ (Lambda f: f())' nicht. – wberry

Antwort

3

Nein, nach den grammar specification nicht möglich:

 
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 

Dekorateure m asst haben ihre Klammern am Ende

2

Ich habe die Vorberechnung Ihrer Vorher- und Nachher-Varianten hinzugefügt (dank des Aufruftricks werden alle Verschlüsse zum Zeitpunkt des Imports erstellt und verwendet, wenn der Dekorator angewendet wird). Die Auswahl hängt von einem optionalen Argument ab der Meta-Decorator und setze einen try/finally-Block ein, um sicherzustellen, dass deine Nach-Events immer ausgelöst werden. Mit diesem Ansatz wird die Frage nach Funktionsattributen strittig.

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

Sicher wird das die ursprünglichen 'fire' ohne Argumente aufrufen, da' invoke (feuert) 'aufgerufen wird, was zu' feuers() 'expandiert, was wiederum zu einem Fehler führt, da das' event' Argument fehlt. – Eric

+1

Der '@ invoke'-Dekorator bewirkt, dass die innere Funktion' closure' die ursprünglichen 'fire' ersetzt. Sobald es definiert ist, kannst du Dinge mit '@fires (myevent, True)' dekorieren und es wird danach feuern. – wberry

+0

Ah, das macht jetzt Sinn, wenn Sie die Argumente verschoben haben. Sind Sie sicher, dass es eine Verbesserung ist? @ Invoke sieht aus wie ein Hack. Fühlt sich so an, als müsste ich einfach eine Klasse verwenden, um beide Versionen zu verpacken. – Eric

1

Ich bin mir nicht sicher, ob es eine Möglichkeit ist, die Syntax, die Sie arbeiten möchten, aber hier ist eine Alternative.

nur ein zusätzliches Argument zu Ihrem fires() Dekorateur hinzufügen, um zu bestimmen, ob es vor oder nach passieren soll:

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 

Und dann ist es wie folgt verwenden:

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

Ja, ich habe darüber nachgedacht, aber es fühlt sich andersherum sauberer an. Wenn ich diesen Weg gehen muss, werde ich wahrscheinlich '' fire.whenCalled (myEvent) ''wählen – Eric

Verwandte Themen