2010-01-19 4 views
8

Warum sind die folgenden beiden Skripte nicht äquivalent?Warum können Python-Dekoratoren nicht über Definitionen hinweg verkettet werden?

(aus einer anderen Frage Genommen: Understanding Python Decorators)

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makebold 
@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## returns <b><i>hello world</i></b> 

und mit einem verzierten Dekorateur:

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## TypeError: wrapped() takes no arguments (1 given) 

Warum will ich wissen? Ich habe einen retry Dekorator geschrieben, um MySQLdb-Ausnahmen zu fangen - wenn die Ausnahme vorübergehend ist (z. B. Timeout), wird sie die Funktion nach dem Einschlafen erneut aufrufen.

Ich habe auch einen modifies_db Dekorateur, der sich um einige Cache-bezogene Housekeeping kümmert. modifies_db ist mit retry dekoriert, so nahm ich an, dass alle mit modifies_db verzierten Funktionen auch implizit wiederholen würden. Was habe ich falsch gemacht?

+0

Gute Frage. Ich bin vor ein paar Monaten in dasselbe Szenario geraten, als ich meinen eigenen Retry Decorator gemacht habe. Es dauerte eine Wiederholung, so dass das Problem ein wenig anders als bei Ihnen vorkam, aber die gleichen Lösungen wie unten gezeigt hatte. –

Antwort

9

Das Problem mit dem zweiten Beispiel ist, dass

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

versucht makeitalic, der Dekorateur, zu dekorieren und nicht wrapped, die Funktion, die es zurückgibt.

können Sie tun, was ich denke, Sie mit so etwas wie dies beabsichtigen:

def makeitalic(fn): 
    @makebold 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

Hier makeitalic verwendet makeboldwrapped zu dekorieren.

+2

+1 Dies zeigt genau das Problem und wie es stattdessen getan werden muss. Grundsätzlich kommt das Problem von dem Gedanken, dass Dekorateure die Zusammensetzung der Funktion übernehmen - aber sie tun es nicht. –

+0

Ja, genau das habe ich versucht, und ja, ich habe die Dekoratoren == funktionale Zusammensetzung missverstanden – RobM

1

Der Grund ist, weil Wrapped() innerhalb von Makebold keine Argumente akzeptiert.

Wenn Sie den Dekorateur so verwenden, kann es einige Probleme verursachen, ich werde ein Beispiel wie Sie erreichen, was Sie wollen, aber geben Sie mir nur einen Moment.

Hier ist ein Arbeitsbeispiel von dem, was Sie brauchen.

def makebold(rewrap=False): 
    if rewrap: 
     def inner(decorator): 
      def rewrapper(func): 
       def wrapped(*args, **kwargs): 
        return "<b>%s</b>" % decorator(func)(*args,**kwargs) 
       return wrapped 
      return rewrapper 
     return inner 

    else: 
     def inner(func): 
      def wrapped(*args, **kwargs): 
       return "<b>%s</b>" % func(*args, **kwargs)  
      return wrapped 
     return inner 

@makebold(rewrap=True) 
def makeitalic(fn): 
    def wrapped(*args, **kwargs): 
     return "<i>%s</i>" % fn(*args, **kwargs) 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

@makebold() 
def hello2(): 
    return "Bob Dole"  

if __name__ == "__main__": 
    print hello() 
    print hello2() 

makebold ist ein bisschen hässlich, aber es zeigt Ihnen, wie ein Dekorateur zu schreiben, der gegebenenfalls ein weiteres Dekorateur wickeln kann. Hier

ist die Ausgabe des obigen Skript:

<b><i>hello world</i></b> 
<b>Bob Dole</b> 

Beachten Sie, dass makebold die einzige rekursive Dekorateur ist. Beachten Sie auch den feinen Unterschied in der Verwendung: @makebold() vs @makeitalic.

+0

-1 ... übermäßig komplex, und "makeitalic" ist ein schlechter Name für etwas, das fett + kursiv tut.Siehe [diese Antwort] (http://stackoverflow.com/a/739665/850830), die dasselbe in einigen Zeilen Code tut. – Ryan

0

Das Problem ist Ersetzen von "makeitalic" (die ein Argument dauert) mit der "Wrapped" -Funktion in "makebold", die Null Argumente.

Verwenden *args, **kwargs auf Argumente weiter unten in der Kette passieren zu:

def wrapped(*args, **kwargs): 
    return "<b>" + fn(*args, **kwargs) + "</b>" 
+0

das Problem ist ein wenig mehr als nur das eigentlich. Umhüllen eines anderen Dekorators hat noch ein paar Probleme. –

Verwandte Themen