Wie kann ich in Python zwei Dekorateure erstellen, die Folgendes tun?
@makebold
@makeitalic
def say():
return "Hello"
... was zurückkehren sollte:
"<b><i>Hello</i></b>"
Ich versuche nicht, HTML
dies in einer realen Anwendung zu tun - ich versuche nur zu verstehen, wie Dekorateure und Dekorationsketten funktionieren.
__name__
und, was das Decorator-Paket betrifft , die Funktionssignatur).*args
und**kwargs
sollte in der Antwort hinzugefügt werden. Dekorierte Funktionen können Argumente haben und gehen verloren, wenn sie nicht angegeben werden.*args
,**kwargs
. Eine einfache Möglichkeit, diese drei Probleme gleichzeitig zu lösen, ist die hierdecopatch
erläuterte Verwendung . Sie können auch, wie bereits von Marius Gedminas erwähnt, verwenden, um die Punkte 2 und 3 zu lösen.decorator
Wenn Sie keine langen Erklärungen haben, lesen Sie die Antwort von Paolo Bergantino .
Grundlagen des Dekorateurs
Pythons Funktionen sind Objekte
Um Dekoratoren zu verstehen, müssen Sie zunächst verstehen, dass Funktionen Objekte in Python sind. Dies hat wichtige Konsequenzen. Mal sehen, warum mit einem einfachen Beispiel:
Denken Sie daran. Wir werden in Kürze darauf zurückkommen.
Eine weitere interessante Eigenschaft von Python-Funktionen ist, dass sie in einer anderen Funktion definiert werden können!
Funktionsreferenzen
Okay, immer noch hier? Nun der lustige Teil ...
Sie haben gesehen, dass Funktionen Objekte sind. Daher Funktionen:
Das heißt, eine Funktion kann eine
return
andere Funktion haben .Es gibt mehr!
Wenn Sie
return
eine Funktion können, können Sie eine als Parameter übergeben:Nun, Sie haben einfach alles, was Sie brauchen, um Dekorateure zu verstehen. Sie sehen, Dekoratoren sind "Wrapper", was bedeutet, dass Sie Code vor und nach der Funktion ausführen können, die sie dekorieren, ohne die Funktion selbst zu ändern.
Handgefertigte Dekorateure
So würden Sie es manuell machen:
Jetzt möchten Sie wahrscheinlich, dass jedes Mal, wenn Sie anrufen
a_stand_alone_function
,a_stand_alone_function_decorated
stattdessen aufgerufen wird. Das ist einfach, überschreiben Sie einfacha_stand_alone_function
mit der Funktion, die zurückgegeben wird vonmy_shiny_new_decorator
:Dekorateure entmystifiziert
Das vorherige Beispiel mit der Decorator-Syntax:
Ja, das ist alles, so einfach ist das.
@decorator
ist nur eine Abkürzung zu:Dekorateure sind nur eine pythonische Variante des Dekorationsmuster . In Python sind mehrere klassische Entwurfsmuster eingebettet, um die Entwicklung zu vereinfachen (z. B. Iteratoren).
Natürlich können Sie Dekorateure ansammeln:
Verwenden der Python Decorator-Syntax:
Die Reihenfolge, in der Sie die Dekorateure einstellen, ist wichtig:
Nun: um die Frage zu beantworten ...
Als Fazit können Sie leicht sehen, wie die Frage zu beantworten ist:
Sie können jetzt einfach glücklich gehen oder Ihr Gehirn ein bisschen mehr verbrennen und die fortgeschrittene Verwendung von Dekorateuren sehen.
Dekorateure auf die nächste Stufe bringen
Argumente an die dekorierte Funktion übergeben
Dekorationsmethoden
Eine nette Sache an Python ist, dass Methoden und Funktionen wirklich gleich sind. Der einzige Unterschied besteht darin, dass Methoden erwarten, dass ihr erstes Argument auf das aktuelle Objekt verweist (
self
).Das heißt, Sie können auf die gleiche Weise einen Dekorateur für Methoden erstellen! Denken Sie daran, Folgendes
self
zu berücksichtigen:Wenn Sie einen Allzweckdekorateur erstellen - einen, den Sie auf jede Funktion oder Methode anwenden, unabhängig von ihren Argumenten -, verwenden Sie einfach
*args, **kwargs
:Argumente an den Dekorateur weitergeben
Großartig, was würden Sie jetzt über die Weitergabe von Argumenten an den Dekorateur selbst sagen?
Dies kann etwas verdreht werden, da ein Dekorateur eine Funktion als Argument akzeptieren muss. Daher können Sie die Argumente der dekorierten Funktion nicht direkt an den Dekorator übergeben.
Bevor wir zur Lösung eilen, schreiben wir eine kleine Erinnerung:
Es ist genau das gleiche. "
my_decorator
" heißt. Wenn Sie@my_decorator
also Python anweisen, die durch die Variable "my_decorator
" gekennzeichnete Funktion "aufzurufen .Das ist wichtig! Das Etikett, das Sie geben, kann direkt auf den Dekorateur verweisen - oder auch nicht .
Lass uns böse werden. ☺
Kein Wunder hier.
Lassen Sie uns genau das Gleiche tun, aber alle lästigen Zwischenvariablen überspringen:
Machen wir es noch kürzer :
Hey, hast du das gesehen? Wir haben einen Funktionsaufruf mit der
@
Syntax " " verwendet! :-)Also zurück zu den Dekorateuren mit Argumenten. Wenn wir Funktionen verwenden können, um den Dekorator im laufenden Betrieb zu generieren, können wir dieser Funktion Argumente übergeben, oder?
Hier ist es: ein Dekorateur mit Argumenten. Argumente können als Variable festgelegt werden:
Wie Sie sehen, können Sie mit diesem Trick wie jede andere Funktion Argumente an den Dekorateur übergeben. Sie können sogar verwenden,
*args, **kwargs
wenn Sie möchten. Aber denken Sie daran, Dekorateure werden nur einmal angerufen . Nur wenn Python das Skript importiert. Sie können die Argumente danach nicht dynamisch festlegen. Wenn Sie "x importieren", ist die Funktion bereits dekoriert , sodass Sie nichts ändern können.Lassen Sie uns üben: Dekorieren eines Dekorateurs
Okay, als Bonus gebe ich Ihnen einen Ausschnitt, damit jeder Dekorateur generisch jedes Argument akzeptiert. Um Argumente zu akzeptieren, haben wir unseren Dekorator mit einer anderen Funktion erstellt.
Wir haben den Dekorateur eingewickelt.
Gibt es noch etwas, das wir kürzlich gesehen haben?
Oh ja, Dekorateure!
Lass uns Spaß haben und einen Dekorateur für die Dekorateure schreiben:
Es kann wie folgt verwendet werden:
Ich weiß, das letzte Mal, als Sie dieses Gefühl hatten, war es, nachdem Sie einem Mann zugehört hatten, der sagte: "Bevor Sie die Rekursion verstehen, müssen Sie zuerst die Rekursion verstehen." Aber jetzt, fühlst du dich nicht gut dabei, das zu meistern?
Best Practices: Dekorateure
Das
functools
Modul wurde in Python 2.5 eingeführt. Es enthält die Funktionfunctools.wraps()
, die den Namen, das Modul und die Dokumentzeichenfolge der dekorierten Funktion in ihren Wrapper kopiert.(Lustige Tatsache:
functools.wraps()
ist ein Dekorateur! ☺)Wie können die Dekorateure nützlich sein?
Nun die große Frage: Wofür kann ich Dekorateure verwenden?
Scheint cool und kraftvoll, aber ein praktisches Beispiel wäre großartig. Nun, es gibt 1000 Möglichkeiten. Klassische Anwendungen sind das Erweitern eines Funktionsverhaltens von einer externen Bibliothek (Sie können es nicht ändern) oder das Debuggen (Sie möchten es nicht ändern, weil es nur vorübergehend ist).
Sie können sie verwenden, um mehrere Funktionen auf DRY-Weise zu erweitern, wie zum Beispiel:
Das Gute an Dekorateuren ist natürlich, dass Sie sie sofort für fast alles verwenden können, ohne sie neu zu schreiben. TROCKEN, sagte ich:
Python bietet sich mehrere Dekorateure:
property
,staticmethod
usw.Dies ist wirklich ein großer Spielplatz.
quelle
__closure__
Attribut) in den Verschluss in der Funktionsrückgabe zu gelangen, um die ursprüngliche nicht dekorierte Funktion herauszuziehen. In dieser Antwort wird eine Beispielverwendung dokumentiert , die beschreibt, wie es möglich ist, eine Dekorationsfunktion unter bestimmten Umständen auf einer niedrigeren Ebene zu injizieren.@decorator
Syntax von Python wird wahrscheinlich am häufigsten verwendet, um eine Funktion durch einen Wrapper-Abschluss zu ersetzen (wie in der Antwort beschrieben). Es kann aber auch die Funktion durch etwas anderes ersetzen. Die eingebauteproperty
,classmethod
undstaticmethod
Dekoratore ersetzen die Funktion mit einem Deskriptor, zum Beispiel. Ein Dekorateur kann auch etwas mit einer Funktion tun, z. B. einen Verweis darauf in einer Registrierung speichern und ihn dann unverändert ohne Wrapper zurückgeben.Alternativ können Sie eine Factory-Funktion schreiben, die einen Dekorator zurückgibt, der den Rückgabewert der dekorierten Funktion in ein Tag einschließt, das an die Factory-Funktion übergeben wird. Zum Beispiel:
So können Sie schreiben:
oder
Persönlich hätte ich den Dekorateur etwas anders geschrieben:
was ergeben würde:
Vergessen Sie nicht die Konstruktion, für die die Dekoratorsyntax eine Abkürzung ist:
quelle
def wrap_in_tag(*kwargs)
damals mit * kwargs codieren@wrap_in_tag('b','i')
Es sieht so aus, als hätten Ihnen die anderen Leute bereits gesagt, wie Sie das Problem lösen können. Ich hoffe, das hilft Ihnen zu verstehen, was Dekorateure sind.
Dekorateure sind nur syntaktischer Zucker.
Diese
erweitert sich auf
quelle
@decorator()
(anstelle von@decorator
) syntaktischer Zucker fürfunc = decorator()(func)
. Dies ist auch gängige Praxis, wenn Sie Dekorateure "on the fly" generieren müssenUnd natürlich können Sie Lambdas auch von einer Dekorationsfunktion zurückgeben:
quelle
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
, um die Dokumentzeichenfolge / Signatur / den Namen vonsay
@wraps
irgendwo anders auf dieser Seite bin, hilft es mir nicht, wenn ich druckehelp(say)
und "Hilfe zur Funktion <lambda>" anstelle von "Hilfe zur Funktion sagen" erhalte .Python-Dekoratoren fügen einer anderen Funktion zusätzliche Funktionen hinzu
Ein kursiver Dekorateur könnte so sein
Beachten Sie, dass eine Funktion innerhalb einer Funktion definiert ist. Grundsätzlich wird eine Funktion durch die neu definierte ersetzt. Zum Beispiel habe ich diese Klasse
Sagen wir nun, ich möchte, dass beide Funktionen "---" drucken, nachdem und bevor sie fertig sind. Ich könnte vor und nach jeder Druckanweisung ein Druck "---" hinzufügen. Aber weil ich mich nicht gerne wiederhole, werde ich einen Dekorateur machen
Jetzt kann ich meine Klasse ändern
Weitere Informationen zu Dekorateuren finden Sie unter http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
quelle
self
Argument wird benötigt, da dasnewFunction()
definierte inaddDashes()
speziell als Methodendekorateur und nicht als allgemeiner Funktionsdekorator konzipiert wurde. Dasself
Argument stellt die Klasseninstanz dar und wird an Klassenmethoden übergeben, unabhängig davon, ob sie diese verwenden oder nicht - siehe Abschnitt Dekorationsmethoden in der Antwort von @ e-satis.functools.wraps
Sie können zwei separate Dekorateure erstellen, die das tun, was Sie möchten, wie direkt unten dargestellt. Beachten Sie die Verwendung von
*args, **kwargs
in der Deklaration derwrapped()
Funktion, die die dekorierte Funktion mit mehreren Argumenten unterstützt (was für die Beispielfunktion nicht wirklich erforderlich istsay()
, aber der Allgemeinheit halber enthalten ist).Aus ähnlichen Gründen wird der
functools.wraps
Dekorator verwendet, um die Metaattribute der umschlossenen Funktion in die der zu dekorierenden zu ändern. Dies macht Fehlermeldungen und eingebettete Funktionsdokumentation (func.__doc__
) zu denen der dekorierten Funktion anstelle vonwrapped()
's.Verfeinerungen
Wie Sie sehen können, enthält diese beiden Dekorateure viel doppelten Code. Angesichts dieser Ähnlichkeit wäre es für Sie besser, stattdessen eine generische zu erstellen, die eigentlich eine Dekorationsfabrik war - mit anderen Worten, eine Dekorationsfunktion, die andere Dekorateure herstellt. Auf diese Weise würde weniger Code wiederholt - und das DRY- Prinzip könnte eingehalten werden.
Um den Code besser lesbar zu machen, können Sie den werkseitig generierten Dekorateuren einen aussagekräftigeren Namen zuweisen:
oder kombinieren Sie sie sogar so:
Effizienz
Während die obigen Beispiele alle Arbeiten ausführen, ist der generierte Code mit einem erheblichen Aufwand in Form von externen Funktionsaufrufen verbunden, wenn mehrere Dekorateure gleichzeitig angewendet werden. Dies spielt möglicherweise keine Rolle, abhängig von der genauen Verwendung (die beispielsweise E / A-gebunden sein kann).
Wenn die Geschwindigkeit der dekorierten Funktion wichtig ist, kann der Overhead auf einen einzelnen zusätzlichen Funktionsaufruf beschränkt werden, indem eine etwas andere Dekorations-Factory-Funktion geschrieben wird, die das gleichzeitige Hinzufügen aller Tags implementiert, sodass Code generiert werden kann, der die zusätzlichen Funktionsaufrufe vermeidet durch Verwendung separater Dekoratoren für jedes Tag.
Dies erfordert mehr Code im Dekorator selbst, wird jedoch nur ausgeführt, wenn er auf Funktionsdefinitionen angewendet wird, nicht später, wenn sie selbst aufgerufen werden. Dies gilt auch, wenn Sie besser lesbare Namen mithilfe der
lambda
zuvor dargestellten Funktionen erstellen . Stichprobe:quelle
Eine andere Möglichkeit, dasselbe zu tun:
Oder flexibler:
quelle
functools.update_wrapper
um zu haltensayhi.__name__ == "sayhi"
Wenn Sie aufgerufen werden, möchten Sie die folgende Funktion:
Zurückgeben:
Einfache Lösung
Um dies am einfachsten zu tun, erstellen Sie Dekorateure, die Lambdas (anonyme Funktionen) zurückgeben, die über der Funktion (Verschlüsse) schließen, und nennen Sie sie:
Verwenden Sie sie nun wie gewünscht:
und nun:
Probleme mit der einfachen Lösung
Aber wir scheinen die ursprüngliche Funktion fast verloren zu haben.
Um es zu finden, müssten wir in die Schließung jedes Lambdas graben, von denen eines im anderen begraben ist:
Wenn wir also diese Funktion dokumentieren oder Funktionen dekorieren möchten, die mehr als ein Argument enthalten, oder nur wissen möchten, welche Funktion wir in einer Debugging-Sitzung betrachtet haben, müssen wir etwas mehr mit unserer tun Verpackung.
Voll funktionsfähige Lösung - Überwindung der meisten dieser Probleme
Wir haben den Dekorateur
wraps
aus demfunctools
Modul in der Standardbibliothek!Es ist bedauerlich, dass es noch einige Boilerplate gibt, aber das ist ungefähr so einfach, wie wir es machen können.
In Python 3 erhalten Sie auch standardmäßig
__qualname__
und__annotations__
zugewiesen.Also jetzt:
Und nun:
Fazit
Wir sehen also,
wraps
dass die Wrapping-Funktion fast alles macht, außer uns genau zu sagen, was die Funktion als Argumente verwendet.Es gibt andere Module, die möglicherweise versuchen, das Problem zu lösen, aber die Lösung befindet sich noch nicht in der Standardbibliothek.
quelle
Um den Dekorateur auf einfache Weise zu erklären:
Mit:
Wann tun:
Das tust du wirklich:
quelle
Ein Dekorateur übernimmt die Funktionsdefinition und erstellt eine neue Funktion, die diese Funktion ausführt und das Ergebnis transformiert.
ist äquivalent zu:
Beispiel:
Diese
ist gleichbedeutend damit
65 <=> 'a'
Um den Dekorateur zu verstehen, ist es wichtig zu beachten, dass der Dekorateur eine neue Funktion do erstellt hat, die inner ist und die Funktion ausführt und das Ergebnis transformiert.
quelle
print(do(65))
undprint(do2(65))
seinA
undA
?Sie können auch Dekorateur in Klasse schreiben
quelle
Diese Antwort wurde schon lange beantwortet, aber ich dachte, ich würde meine Dekorationsklasse teilen, was das Schreiben neuer Dekorateure einfach und kompakt macht.
Zum einen macht dies das Verhalten von Dekorateuren sehr deutlich, aber es macht es auch einfach, neue Dekorateure sehr präzise zu definieren. Für das oben aufgeführte Beispiel können Sie es dann wie folgt lösen:
Sie können es auch verwenden, um komplexere Aufgaben auszuführen, z. B. einen Dekorator, bei dem die Funktion automatisch rekursiv auf alle Argumente in einem Iterator angewendet wird:
Welche Drucke:
Beachten Sie, dass dieses Beispiel den
list
Typ nicht in die Instanziierung des Dekorateurs einbezog. In der endgültigen Druckanweisung wird die Methode also auf die Liste selbst angewendet, nicht auf die Elemente der Liste.quelle
Hier ist ein einfaches Beispiel für die Verkettung von Dekorateuren. Beachten Sie die letzte Zeile - sie zeigt, was unter der Decke vor sich geht.
Die Ausgabe sieht aus wie:
quelle
Apropos Zählerbeispiel - wie oben angegeben, wird der Zähler von allen Funktionen geteilt, die den Dekorator verwenden:
Auf diese Weise kann Ihr Dekorator für verschiedene Funktionen wiederverwendet werden (oder verwendet werden, um dieselbe Funktion mehrmals zu dekorieren :)
func_counter1 = counter(func); func_counter2 = counter(func)
, und die Zählervariable bleibt für jede privat.quelle
Dekorieren Sie Funktionen mit unterschiedlicher Anzahl von Argumenten:
Ergebnis:
quelle
def wrapper(*args, **kwargs):
und unterstützt werdenfn(*args, **kwargs)
.Die Antwort von Paolo Bergantino hat den großen Vorteil, nur die stdlib zu verwenden, und funktioniert für dieses einfache Beispiel, bei dem es weder Dekorationsargumente noch dekorierte Funktionsargumente gibt .
Es gibt jedoch drei Hauptbeschränkungen, wenn Sie allgemeinere Fälle angehen möchten:
makestyle(style='bold')
Dekorateurs nicht trivial.@functools.wraps
die Signatur nicht bei . Wenn also schlechte Argumente angegeben werden, werden sie ausgeführt und können einen anderen Fehler als den üblichen auslösenTypeError
.@functools.wraps
, auf ein Argument zuzugreifen, das auf seinem Namen basiert . In der Tat kann das Argument in*args
, in**kwargs
oder überhaupt nicht erscheinen (wenn es optional ist).Ich schrieb
decopatch
, um das erste Problem zu lösen, und schriebmakefun.wraps
, um die beiden anderen zu lösen. Beachten Sie, dassmakefun
der gleiche Trick wie die berühmtedecorator
Bibliothek verwendet wird.Auf diese Weise würden Sie einen Dekorateur mit Argumenten erstellen und wirklich signaturerhaltende Wrapper zurückgeben:
decopatch
bietet Ihnen zwei weitere Entwicklungsstile, mit denen die verschiedenen Python-Konzepte je nach Ihren Vorlieben ausgeblendet oder angezeigt werden. Der kompakteste Stil ist der folgende:In beiden Fällen können Sie überprüfen, ob der Dekorateur wie erwartet funktioniert:
Weitere Informationen finden Sie in der Dokumentation .
quelle