7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. Python

Schreiben Sie Ihre eigenen Python-Dekorateure

Read Time: 10 mins

German (Deutsch) translation by Federicco Ancie (you can also view the original English article)

Überblick

In dem Artikel Deep Dive Into Python Decorators stellte ich das Konzept der Python-Dekorateure vor, demonstrierte viele coole Dekorateure und erklärte, wie man sie verwendet.

In diesem Tutorial zeige ich Ihnen, wie Sie Ihre eigenen Dekorateure schreiben. Wie Sie sehen werden, gibt Ihnen das Schreiben Ihrer eigenen Dekorateure viel Kontrolle und ermöglicht viele Funktionen. Ohne Dekorateure würden diese Funktionen eine Menge fehleranfälliger und sich wiederholender Boilerplates erfordern, die Ihren Code oder völlig externe Mechanismen wie die Codegenerierung überladen.

Eine kurze Zusammenfassung, wenn Sie nichts über Dekorateure wissen. Ein Dekorator ist eine aufrufbare (Funktion, Methode, Klasse oder ein Objekt mit einer call()-Methode), die eine aufrufbare als Eingabe akzeptiert und eine aufrufbare als Ausgabe zurückgibt. In der Regel führt der zurückgegebene Callable etwas vor und / oder nach dem Aufruf des Callable-Eingangs aus. Sie wenden den Dekorateur mit dem @ an Syntax. Viele Beispiele folgen in Kürze...

Der Hello World Decorator

Beginnen wir mit einem "Hello World!" - Dekorateur. Dieser Dekorateur ersetzt jedes dekorierte Callable vollständig durch eine Funktion, die nur "Hello World!" druckt.

Das ist es. Lassen Sie es uns in Aktion sehen und dann die verschiedenen Teile und ihre Funktionsweise erklären. Angenommen, wir haben die folgende Funktion, die zwei Zahlen akzeptiert und ihr Produkt druckt:

Wenn Sie aufrufen, erhalten Sie, was Sie erwarten:

Dekorieren wir es mit unserem hello_world-Dekorateur, indem wir die Multiplikationsfunktion mit @hello_world kommentieren.

Wenn Sie jetzt Multiplizieren mit Argumenten (einschließlich falscher Datentypen oder falscher Anzahl von Argumenten) aufrufen, wird das Ergebnis immer "Hello World!" Gedruckt.

OK. Wie funktioniert es? Die ursprüngliche Multiplikationsfunktion wurde vollständig durch die verschachtelte dekorierte Funktion im hello_world-Dekorator ersetzt. Wenn wir die Struktur des hello_world-Dekorators analysieren, werden Sie feststellen, dass er die aufrufbare Eingabe f akzeptiert (die in diesem einfachen Dekorator nicht verwendet wird). Er definiert eine verschachtelte Funktion namens dekoriert, die eine beliebige Kombination von Argumenten und Schlüsselwortargumenten akzeptiert (def decorated (*args, **kwargs)), und schließlich wird die dekorierte Funktion zurückgegeben.

Schreibfunktions- und Methodendekoratoren

Es gibt keinen Unterschied zwischen dem Schreiben einer Funktion und einem Methodendekorateur. Die Dekorationsdefinition ist dieselbe. Die aufrufbare Eingabe ist entweder eine reguläre Funktion oder eine gebundene Methode.

Lassen Sie uns das überprüfen. Hier ist ein Dekorateur, der nur die aufrufbare Eingabe druckt und vor dem Aufrufen eingibt. Dies ist sehr typisch für einen Dekorateur, der eine Aktion ausführt und mit dem Aufrufen des ursprünglichen Callables fortfährt.

Beachten Sie die letzte Zeile, die die Eingabe generisch aufruft und das Ergebnis zurückgibt. Dieser Dekorator ist nicht aufdringlich in dem Sinne, dass Sie jede Funktion oder Methode in einer Arbeitsanwendung dekorieren können, und die Anwendung funktioniert weiterhin, da die dekorierte Funktion das Original aufruft und zuvor nur einen kleinen Nebeneffekt hat.

Lassen Sie es uns in Aktion sehen. Ich werde sowohl unsere Multiplikationsfunktion als auch eine Methode dekorieren.

Wenn wir die Funktion und die Methode aufrufen, wird die aufrufbare Datei gedruckt und sie führen dann ihre ursprüngliche Aufgabe aus:

Dekorateure mit Argumenten

Dekorateure können auch Argumente annehmen. Diese Möglichkeit, den Betrieb eines Dekorateurs zu konfigurieren, ist sehr leistungsfähig und ermöglicht es Ihnen, denselben Dekorator in vielen Kontexten zu verwenden.

Angenommen, Ihr Code ist viel zu schnell und Ihr Chef bittet Sie, ihn etwas zu verlangsamen, weil Sie die anderen Teammitglieder schlecht aussehen lassen. Schreiben wir einen Dekorateur, der misst, wie lange eine Funktion ausgeführt wird. Wenn sie in weniger als einer bestimmten Anzahl von Sekunden t ausgeführt wird, wartet sie, bis t Sekunden ablaufen, und kehrt dann zurück.

Was jetzt anders ist, ist, dass der Dekorateur selbst ein Argument t verwendet, das die minimale Laufzeit bestimmt, und verschiedene Funktionen mit unterschiedlichen minimalen Laufzeiten dekoriert werden können. Außerdem werden Sie feststellen, dass beim Einführen von Dekorationsargumenten zwei Verschachtelungsebenen erforderlich sind:

Packen wir es aus. Der Dekorator selbst - die Funktion minimum_runtime verwendet ein Argument t, das die minimale Laufzeit für den dekorierten Aufruf darstellt. Die aufrufbare Eingabe f wurde auf die verschachtelte decorated Funktion "gedrückt", und die aufrufbaren Eingabeargumente wurden auf einen weiteren verschachtelten Funktions-wrapper "gedrückt".

Die eigentliche Logik findet innerhalb der wrapper-Funktion statt. Die Startzeit wird aufgezeichnet, das ursprüngliche aufrufbare f wird mit seinen Argumenten aufgerufen und das Ergebnis wird gespeichert. Dann wird die Laufzeit überprüft. Wenn sie unter dem Minimum t liegt, schläft sie für den Rest der Zeit und kehrt dann zurück.

Um es zu testen, erstelle ich einige Funktionen, die Multiplizieren aufrufen und sie mit unterschiedlichen Verzögerungen dekorieren.

Jetzt rufe ich Multiplikation direkt sowie die langsameren Funktionen auf und messe die Zeit.

Hier ist die Ausgabe:

Wie Sie sehen können, dauerte die ursprüngliche Multiplikation fast keine Zeit, und die langsameren Versionen wurden tatsächlich entsprechend der angegebenen Mindestlaufzeit verzögert.

Eine weitere interessante Tatsache ist, dass die ausgeführte dekorierte Funktion der Wrapper ist. Dies ist sinnvoll, wenn Sie der Definition des dekorierten folgen. Dies könnte jedoch ein Problem sein, insbesondere wenn es sich um Stapeldekorateure handelt. Der Grund dafür 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 es werden Ratschläge zu Best Practices gegeben.

Objektdekorateure

Sie können Objekte auch als Dekorateure verwenden oder Objekte von Ihren Dekorateuren zurückgeben. Die einzige Voraussetzung ist, dass sie über eine __call __()-Methode verfügen, sodass sie aufgerufen werden können. Hier ist ein Beispiel für einen objektbasierten Dekorateur, der zählt, wie oft seine Zielfunktion aufgerufen wird:

Hier ist es in Aktion:

Auswahl zwischen funktionsbasierten und objektbasierten Dekorateuren

Dies ist meist eine Frage der persönlichen Präferenz. Verschachtelte Funktionen und Funktionsabschlüsse bieten die gesamte Statusverwaltung, die Objekte bieten. Manche Menschen fühlen sich mit Klassen und Gegenständen wohler.

Im nächsten Abschnitt werde ich auf gut erzogene Dekorateure eingehen, und objektbasierte Dekorateure benötigen ein wenig zusätzliche Arbeit, um sich gut zu benehmen.

Gut erzogene Dekorateure

Allzweckdekorateure können oft gestapelt werden. Beispielsweise:

Beim Stapeln von Dekorateuren erhält der äußere Dekorateur (in diesem Fall dekorator_1) den vom inneren Dekorateur (dekorator_2) zurückgegebenen Anruf. Wenn decorator_1 in irgendeiner Weise vom Namen, den Argumenten oder der Dokumentzeichenfolge der ursprünglichen Funktion abhängt und decorator_2 naiv implementiert ist, sieht decorator_2 nicht die korrekten Informationen aus der ursprünglichen Funktion, sondern nur den von decorator_2 zurückgegebenen Aufruf.

Hier ist beispielsweise ein Dekorateur, der überprüft, ob der Name seiner Zielfunktion in Kleinbuchstaben geschrieben ist:

Dekorieren wir damit eine Funktion:

Das Aufrufen von Foo() führt zu einer Behauptung:

Wenn wir jedoch den check_lowercase-Dekorator über einen Dekorator wie hello_world stapeln, der eine verschachtelte Funktion namens "dekoriert" zurückgibt, ist das Ergebnis ganz anders:

Der check_lowercase-Dekorateur hat keine Zusicherung gemacht, da der Funktionsname "Foo" nicht angezeigt wurde. Dies ist ein ernstes Problem. Das richtige Verhalten für einen Dekorateur besteht darin, so viele Attribute der ursprünglichen Funktion wie möglich beizubehalten.

Mal sehen, wie es geht. Ich werde jetzt einen Shell-Dekorator erstellen, der seine Eingabe einfach als aufrufbar bezeichnet, aber alle Informationen aus der Eingabefunktion beibehält: den Funktionsnamen, alle Attribute (falls ein innerer Dekorator einige benutzerdefinierte Attribute hinzugefügt hat) und die Dokumentzeichenfolge.

Jetzt funktionieren Dekoratoren, die über dem passthrough-Dekorator gestapelt sind, so, als hätten sie die Zielfunktion direkt dekoriert.

Verwenden des @wraps Decorator

Diese Funktionalität ist so nützlich, dass die Standardbibliothek im functools-Modul einen speziellen Dekorator namens "wraps" hat, mit dem Sie geeignete Dekoratoren schreiben können, die gut mit anderen Dekoratoren zusammenarbeiten. Sie dekorieren einfach die zurückgegebene Funktion in Ihrem Dekorator mit @wraps(f). Sehen Sie, wie viel prägnanter passthrough bei der Verwendung von wraps aussieht:

Ich empfehle dringend, es immer zu verwenden, es sei denn, Ihr Dekorateur ist darauf ausgelegt, einige dieser Attribute zu ändern.

Klassendekorateure schreiben

Klassendekorateure wurden in Python 3.0 eingeführt. Sie arbeiten mit einer ganzen Klasse. Ein Klassendekorator wird aufgerufen, wenn eine Klasse definiert wird und bevor Instanzen erstellt werden. Dadurch kann der Klassendekorateur so ziemlich jeden Aspekt der Klasse ändern. Normalerweise fügen Sie mehrere Methoden hinzu oder dekorieren sie.

Lassen Sie uns gleich zu einem ausgefallenen Beispiel springen: Angenommen, Sie haben eine Klasse namens "AwesomeClass" mit einer Reihe öffentlicher Methoden (Methoden, deren Name nicht mit einem Unterstrich wie init beginnt) und Sie haben eine auf Unittests basierende Testklasse namens "AwesomeClassTest". AwesomeClass ist nicht nur fantastisch, sondern auch sehr kritisch. Sie möchten sicherstellen, dass jemand, der AwesomeClass eine neue Methode hinzufügt, auch eine entsprechende Testmethode zu AwesomeClassTest hinzufügt. Hier ist die AwesomeClass:

Hier ist der AwesomeClassTest:

Wenn jemand eine awesome_3-Methode mit einem Fehler hinzufügt, werden die Tests trotzdem bestanden, da es keinen Test gibt, der awesome_3 aufruft.

Wie können Sie sicherstellen, dass es für jede öffentliche Methode immer eine Testmethode gibt? Du schreibst natürlich einen Klassendekorateur. Der Klassendekorateur @ensure_tests dekoriert den AwesomeClassTest und stellt sicher, dass jede öffentliche Methode über eine entsprechende Testmethode verfügt.

Das sieht ziemlich gut aus, aber es gibt ein Problem. Klassendekorateure akzeptieren nur ein Argument: die dekorierte Klasse. Der Dekorator ensure_tests benötigt zwei Argumente: die Klasse und die Zielklasse. Ich konnte keinen Weg finden, Klassendekorateure mit Argumenten zu haben, die Funktionsdekorateuren ähneln. Hab keine Angst. Python hat nur für diese Fälle die Funktion functools.partial.

Das Ausführen der Tests führt zum Erfolg, da alle öffentlichen Methoden, awesome_1 und awesome_2, über die entsprechenden Testmethoden test_awesome_1 und test_awesome_2 verfügen.

Fügen wir eine neue Methode awesome_3 ohne entsprechenden Test hinzu und führen Sie die Tests erneut aus.

Das erneute Ausführen der Tests führt zu folgender Ausgabe:

Der Klassendekorateur hat die Nichtübereinstimmung festgestellt und Sie laut und deutlich benachrichtigt.

Abschluss

Das Schreiben von Python-Dekoratoren macht viel Spaß und ermöglicht es Ihnen, unzählige Funktionen auf wiederverwendbare Weise zu kapseln. Um Dekorateure optimal nutzen und auf interessante Weise kombinieren zu können, müssen Sie sich der Best Practices und Redewendungen bewusst sein. Klassendekoratoren in Python 3 fügen eine ganz neue Dimension hinzu, indem sie das Verhalten vollständiger Klassen anpassen.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.