In dem Artikel Deep Dive Into in Python Decorators habe ich das Konzept der Python-Dekorateure vorgestellt, viele coole Dekorateure gezeigt und deren Verwendung erklärt.
In diesem Tutorial zeige ich Ihnen, wie Sie Ihre eigenen Dekorateure schreiben. Wenn Sie Ihre eigenen Dekorateure schreiben, haben Sie viel Kontrolle und viele Möglichkeiten. Ohne Dekorateure würden diese Funktionen viele fehleranfällige und sich wiederholende Boilerplates erfordern, die Ihren Code oder völlig externe Mechanismen wie die Codegenerierung durcheinanderbringen.
Eine kurze Zusammenfassung, wenn Sie nichts über Dekorateure wissen. Ein Dekorator ist eine aufrufbare Funktion (Funktion, Methode, Klasse oder Objekt mit einem Anruf() -Methode), die ein Aufrufbares als Eingabe akzeptiert und ein Aufrufbares als Ausgabe zurückgibt. Normalerweise führt der zurückgegebene aufrufbare Befehl vor und / oder nach dem Aufruf der aufrufbaren Eingabe etwas aus. Sie wenden den Dekorateur mit dem @ an.
Beginnen wir mit einer "Hallo Welt!" Dekorateur. Dieser Dekorateur ersetzt jeden dekorierten Callable vollständig durch eine Funktion, die nur "Hello World!".
python def hello_world (f): def verziert (* args, ** kwargs): print 'Hello World!' verziert zurückkehren
Das ist es. Lassen Sie uns es in Aktion sehen und dann die verschiedenen Teile erklären und wie es funktioniert. Angenommen, wir haben die folgende Funktion, die zwei Zahlen akzeptiert und ihr Produkt druckt:
python def multiplizieren (x, y): x * y drucken
Wenn Sie aufrufen, erhalten Sie, was Sie erwarten:
multiplizieren (6, 7) 42
Lass es uns mit unserem schmücken Hallo Welt Dekorateur durch Kommentieren der multiplizieren Funktion mit @Hallo Welt
.
python @hello_world def multiplizieren (x, y): x * y drucken
Nun, wenn Sie anrufen multiplizieren Bei allen Argumenten (einschließlich falscher Datentypen oder falscher Anzahl von Argumenten) lautet das Ergebnis immer "Hello World!" gedruckt.
"Python multiplizieren (6, 7) Hello World!
multiplizieren () Hallo Welt!
multiplizieren Sie ('zzz') Hallo Welt! "
OK. Wie funktioniert es? Die ursprüngliche Multiplikationsfunktion wurde vollständig durch die verschachtelte Dekorationsfunktion im ersetzt Hallo Welt Dekorateur. Wenn wir die Struktur der analysieren Hallo Welt Dekorateur dann werden Sie sehen, dass er die aufrufbare Eingabe akzeptiert f (was in diesem einfachen Dekorateur nicht verwendet wird), definiert eine verschachtelte Funktion, die aufgerufen wird verziert Das akzeptiert jede Kombination von Argumenten und Schlüsselwortargumenten (def verziert (* args, ** kwargs)
), und schließlich wird das zurückgegeben verziert Funktion.
Es gibt keinen Unterschied zwischen dem Schreiben einer Funktion und einem Methodendekorateur. Die Dekoratordefinition wird dieselbe sein. Die aufrufbare Eingabe ist entweder eine reguläre Funktion oder eine gebundene Methode.
Lassen Sie uns das überprüfen. Hier ist ein Dekorateur, der die aufrufbare Eingabe und den Typ vor dem Aufruf druckt. Dies ist sehr typisch für einen Dekorateur, der eine Aktion ausführt und mit dem Aufruf des ursprünglichen Aufrufbaren fortfährt.
python def print_callable (f): def Dekor (* args, ** kwargs): print f, Typ (f) return f (* args, ** kwargs) return dekoriert
Beachten Sie die letzte Zeile, die die aufrufbare Eingabe auf generische Weise aufruft und das Ergebnis zurückgibt. Dieser Dekorateur ist nicht aufdringlich in dem Sinne, dass Sie jede Funktion oder Methode in einer funktionierenden Anwendung dekorieren können. Die Anwendung funktioniert weiterhin, da die dekorierte Funktion das Original aufruft und nur einen kleinen Nebeneffekt aufweist.
Lass es uns in Aktion sehen. Ich werde sowohl unsere Multiplikationsfunktion als auch eine Methode dekorieren.
"python @print_callable def multiplizieren (x, y): x * y drucken
Klasse A (Objekt): @print_callable def foo (self): "foo () hier drucken"
Wenn wir die Funktion und die Methode aufrufen, wird das Callable gedruckt und dann führen sie ihre ursprüngliche Aufgabe aus:
"Python-Multiplikation (6, 7)
A (). Foo ()
Dekorateure können auch Argumente annehmen. Diese Möglichkeit, den Betrieb eines Dekorators zu konfigurieren, ist sehr leistungsfähig und ermöglicht es Ihnen, denselben Dekorator in vielen Zusammenhängen zu verwenden.
Angenommen, Ihr Code ist viel zu schnell und Ihr Chef bittet Sie, den Code etwas zu verlangsamen, weil Sie die anderen Teammitglieder schlecht aussehen lassen. Lassen Sie uns einen Dekorator schreiben, der misst, wie lange eine Funktion läuft und ob sie in weniger als einer bestimmten Anzahl von Sekunden ausgeführt wird t, es wird warten, bis t Sekunden abgelaufen sind, und dann zurückkehren.
Was jetzt anders ist, ist, dass der Dekorateur selbst ein Argument führt t das bestimmt die minimale Laufzeit, und verschiedene Funktionen können mit unterschiedlichen minimalen Laufzeiten versehen werden. Sie werden auch feststellen, dass bei der Einführung von Decorator-Argumenten zwei Schachtelungsebenen erforderlich sind:
"Pythonimportzeit
def minimum_runtime (t): def Dekor (f): def Wrapper (args, ** kwargs): start = time.time () result = f (args, ** kwargs) runtime = time.time () - Startet die Laufzeit < t: time.sleep(t - runtime) return result return wrapper return decorated"
Lass es uns auspacken. Der Dekorateur selbst - die Funktion Mindestlaufzeit nimmt ein Argument an t, was die minimale Laufzeit für das verzierte Callable darstellt. Die Eingabe kann aufgerufen werden f wurde auf die verschachtelten "heruntergedrückt" verziert Funktion und die aufrufbaren Argumente der Eingabe wurden zu einer weiteren verschachtelten Funktion "verschoben" Verpackung.
Die eigentliche Logik findet innerhalb der statt Verpackung Funktion. Die Startzeit wird aufgezeichnet, das Original kann abgerufen werden f wird mit seinen Argumenten aufgerufen und das Ergebnis wird gespeichert. Dann wird die Laufzeit geprüft und ob sie unter dem Minimum liegt t dann schläft es für den Rest der Zeit und kehrt dann zurück.
Um es zu testen, erstelle ich ein paar Funktionen, die mehrfach aufgerufen und mit unterschiedlichen Verzögerungen verziert werden.
"python @minimum_runtime (1) def slow_multiply (x, y): multiplizieren (x, y)
@minimum_runtime (3) def langsamer_multiply (x, y): multiplizieren (x, y) "
Ich werde jetzt anrufen multiplizieren direkt sowie die langsameren Funktionen und messen die Zeit.
"Pythonimportzeit
funcs = [multiplizieren, langsam_multiply, langsamer_multiply] für f in funcs: start = time.time () f (6, 7) print f, time.time () - start "
Hier ist die Ausgabe:
plain 42
Wie Sie sehen, hat das ursprüngliche Multiplizieren fast keine Zeit in Anspruch genommen, und die langsameren Versionen waren tatsächlich entsprechend der angegebenen Mindestlaufzeit verzögert.
Eine andere interessante Tatsache ist, dass die ausgeführte dekorierte Funktion der Wrapper ist, was sinnvoll ist, wenn Sie der Definition des dekorierten folgen. Aber das könnte ein Problem sein, besonders wenn es um Stapeldekorateure geht. Der Grund ist, dass viele Dekorateure auch ihre aufrufbaren Eingaben überprüfen und ihren Namen, ihre Signatur und ihre Argumente überprüfen. In den folgenden Abschnitten wird dieses Problem behandelt und Empfehlungen für bewährte Verfahren gegeben.
Sie können Objekte auch als Dekorateure verwenden oder Objekte von Ihren Dekorateuren zurückgeben. Die einzige Voraussetzung ist, dass sie eine haben __Anruf__() Methode, also sind sie aufrufbar. Hier ein Beispiel für einen objektbasierten Dekorateur, der zählt, wie oft seine Zielfunktion aufgerufen wird:
Python-Klasse Counter (Objekt): def __init __ (self, f): self.f = f self.called = 0 def __call__ (self, * args, ** kwargs): self.called + = 1 return self.f (* args, ** kwargs)
Hier ist es in Aktion:
"python @Counter def bbb (): druckt 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
print bbb.called 3 "
Dies ist meistens eine Frage der persönlichen Vorlieben. Verschachtelte Funktionen und Funktionsschließungen bieten die gesamte Zustandsverwaltung, die Objekte bieten. Manche Menschen fühlen sich mit Klassen und Objekten wohler.
Im nächsten Abschnitt werde ich auf gut erzogene Dekorateure eingehen, und objektbasierte Dekorateure benötigen etwas mehr Arbeit, um gut benommen zu sein.
Mehrzweck-Dekorateure können oft gestapelt werden. Zum Beispiel:
python @ decorator_1 @ decorator_2 def foo (): drucke 'foo () hier'
Beim Stapeln von Dekorateuren erhält der Außendekorateur (in diesem Fall Dekorateur_1) die Callable, die vom Innendekorateur (Decorator_2) zurückgegeben wird. Wenn decorator_1 in gewisser Weise von Name, Argumenten oder docstring der ursprünglichen Funktion abhängt und decorator_2 naiv implementiert wird, werden bei decorator_2 nicht die korrekten Informationen der ursprünglichen Funktion angezeigt, sondern nur die von decorator_2 zurückgegebene aufrufbare Funktion.
Hier ist zum Beispiel ein Dekorateur, der überprüft, ob der Name der Zielfunktion nur aus Kleinbuchstaben besteht:
python def check_lowercase (f): def decor (* args, ** kwargs): f.func_name == f.func_name.lower () f (* args, ** kwargs) wird mit dekoriert zurückgegeben
Lassen Sie uns damit eine Funktion dekorieren:
python @check_lowercase def Foo (): 'Foo () hier drucken'
Das Aufrufen von Foo () führt zu einer Assertion:
"plain In [51]: Foo () - AssertionError Traceback (letzter Aufruf zuletzt)