Dekorateure: Die mächtigste Technik in Python
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Detaillierte Erklärung von Python-Dekorateuren
I. Was sind Dekorateure
In Python ist ein Dekorateur im Wesentlichen eine Python-Funktion. Er verfügt über einzigartige Fähigkeiten, die es ihm ermöglichen, anderen Funktionen zusätzliche Funktionalität hinzuzufügen, ohne deren ursprünglichen Code zu ändern. Der Rückgabewert eines Dekorateurs ist ebenfalls ein Funktionsobjekt. Vereinfacht ausgedrückt ist es eine Funktion, die speziell dafür entwickelt wurde, eine andere Funktion zurückzugeben.
Dekorateure spielen eine entscheidende Rolle in vielen Szenarien, in denen aspektorientierte Anforderungen bestehen. Zum Beispiel:
- Einfügen von Protokollen: Erleichtert die Aufzeichnung des Ausführungsprozesses und relevanter Informationen von Funktionen, was für das Debuggen und die Systemüberwachung hilfreich ist.
- Leistungstests: Kann die Ausführungszeit einer Funktion berechnen und so ihre Leistung bewerten.
- Transaktionsverarbeitung: Stellt sicher, dass eine Reihe von Operationen entweder alle erfolgreich sind oder alle fehlschlagen, wodurch Datenkonsistenz und -integrität gewährleistet werden.
- Caching: Cacht für Funktionen mit hohen Rechenkosten deren Berechnungsergebnisse. Wenn das gleiche Ergebnis das nächste Mal auftritt, wird direkt der gecachte Wert zurückgegeben, was die Effizienz verbessert.
- Berechtigungsprüfung: Überprüft, ob ein Benutzer vor der Ausführung einer Funktion die entsprechenden Berechtigungen besitzt, wodurch die Sicherheit des Systems gewährleistet wird.
Dekorateure bieten eine hervorragende Designlösung für die Lösung solcher Probleme. Durch die Verwendung von Dekorateuren können wir einen großen Teil des Codes extrahieren, der nicht mit der Kernfunktionalität der Funktion zusammenhängt, aber wiederholt auftritt, wodurch eine hochgradige Wiederverwendung des Codes erreicht wird.
Zusammenfassend lässt sich sagen, dass die Kernfunktion eines Dekorateurs darin besteht, bestehenden Objekten zusätzliche Funktionalität hinzuzufügen, wodurch die Codestruktur klarer und die Funktionen umfangreicher und flexibler werden.
II. Warum werden Dekorateure benötigt
(I) Ein einfaches Beispiel
Betrachten Sie zunächst eine einfache Funktion:
def foo(): print('i am foo')
Diese Funktion gibt einfach die Zeichenkette i am foo
aus.
(II) Hinzufügen von Anforderungen
Nun gibt es eine neue Anforderung, das Ausführungsprotokoll der Funktion aufzuzeichnen. Fügen Sie also protokollbezogenen Code zum Code hinzu:
def foo(): print('i am foo') print("foo is running")
An diesem Punkt hat die Funktion foo
zusätzlich zu ihrer ursprünglichen Funktionalität die Funktion zum Drucken von Protokollen hinzugefügt.
(III) Anforderungen für weitere Funktionen
Angenommen, es gibt 100 Funktionen, die alle solche Protokollierungsanforderungen hinzufügen müssen, und in Zukunft muss möglicherweise die Anforderung hinzugefügt werden, Protokolle vor der Ausführung für diese 100 Funktionen auszugeben. Wenn wir den Funktionscode einzeln ändern, wird eine große Menge an doppeltem Code generiert, was offensichtlich keine gute Lösung ist.
Um das Schreiben von doppeltem Code zu reduzieren, können wir eine Funktion neu definieren, um protokollbezogene Operationen speziell zu verarbeiten. Nachdem die Protokollverarbeitung abgeschlossen ist, wird der eigentliche Geschäftscode ausgeführt. Ein Beispiel ist das folgende:
def use_logging(func): print("%s is running" % func.__name__) func() def bar(): print('i am bar') use_logging(bar)
Das laufende Ergebnis ist:
bar is running
i am bar
In diesem Beispiel ist die Funktion use_logging
ein Dekorateur. Sie umschließt die func
, die die eigentliche Geschäftsmethode innerhalb der Funktion ausführt. Formal scheint es, dass die Funktion bar
von use_logging
dekoriert wird. Die Protokollierungsoperationen beim Betreten und Verlassen der Funktion werden als Aspekt bezeichnet, und diese Programmiermethode wird als Aspektorientierte Programmierung bezeichnet.
Durch diese Funktion use_logging
haben wir der Funktion erfolgreich eine Protokollierungsfunktion hinzugefügt. In Zukunft müssen wir, egal wie viele Funktionen eine Protokollierungsfunktion hinzufügen müssen oder wenn wir das Protokollformat ändern müssen, nur die Funktion use_logging
ändern und use_logging(die dekorierte Funktion)
aufrufen, um den gewünschten Effekt zu erzielen. Zum Beispiel:
def use_logging(func): print("%s is running" % func.__name__) return func @use_logging def bar(): print('i am bar') bar()
III. Einführung in grundlegende Dekorateure
(I) Dekorateur-Syntax-Zucker
Python bietet das Symbol @
als Syntax-Zucker für Dekorateure, was es uns erleichtert, Dekorationsfunktionen anzuwenden. Es gibt jedoch eine Voraussetzung für die Verwendung von Syntax-Zucker, nämlich dass die Dekorationsfunktion ein Funktionsobjekt zurückgeben muss. Daher umschließen wir die zu dekorierende Funktion normalerweise mit einer inneren Funktion und geben diese innere Funktion zurück.
Nehmen Sie den folgenden Code als Beispiel. Der Dekorateur use_logging
ist gleichbedeutend damit, zuerst diese Funktion auszuführen und dann die dekorierte Funktion bar
zurückzugeben. Wenn also bar()
aufgerufen wird, ist dies tatsächlich gleichbedeutend mit der Ausführung von zwei Funktionen, was gleichbedeutend ist mit dem direkten Aufruf von use_logging(bar)()
.
def use_logging(func): def _deco(): print("%s is running" % func.__name__) func() return _deco @use_logging def bar(): print('i am bar') bar()
(II) Dekorieren von Funktionen mit Parametern
Wenn unsere Funktion zwei Parameter empfangen und Berechnungen durchführen muss, müssen wir entsprechende Änderungen an der inneren Funktion vornehmen, um die beiden übergebenen Parameter a
und b
zu empfangen. Zu diesem Zeitpunkt ist der Aufruf von bar(1, 2)
gleichbedeutend mit dem Aufruf von use_logging(bar)(1, 2)
. Der Beispielcode ist wie folgt:
def use_logging(func): def _deco(a, b): print("%s is running" % func.__name__) func(a, b) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 2)
In der Praxis können die Anzahl und die Typen der Parameter der Funktionen, die wir dekorieren, jedoch variieren. Es ist offensichtlich nicht wissenschaftlich, den Dekorateur jedes Mal für unterschiedliche Parametersituationen zu ändern. Um dieses Parameterproblem zu lösen, können wir die Python-Parameter variabler Länge *args
und **kwargs
verwenden.
(III) Ungewisse Anzahl von Funktionsparametern
Im Folgenden ist die Version des Dekorateurs ohne Parameter aufgeführt, und dieses Format eignet sich zum Dekorieren von Funktionen ohne Parameter. Durch die Verwendung von *args
und **kwargs
kann sich unser Dekorateur bereits an Parameter verschiedener Längen und Typen anpassen, d. h. diese Version des Dekorateurs kann jede Art von parameterfreier Funktion dekorieren. Der Beispielcode ist wie folgt:
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) @use_logging def foo(a, b, c): print('i am bar:%s' % (a + b + c)) bar(1, 2) foo(1, 2, 3)
(IV) Dekorateure mit Parametern
In einigen Fällen müssen wir dem Dekorateur Parameter übergeben. Dies erfordert das Schreiben einer Higher-Order-Funktion, die einen Dekorateur zurückgibt, was relativ komplex zu implementieren ist. Zum Beispiel:
#! /usr/bin/env python def use_logging(level): def _deco(func): def __deco(*args, **kwargs): if level == "warn": print "%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3) # Equivalent to use_logging(level="warn")(bar)(1, 3)
(V) functools.wraps
Die Verwendung von Dekorateuren nutzt den Code in hohem Maße wieder, hat aber den Nachteil, dass die Metainformationen der ursprünglichen Funktion verloren gehen. Zum Beispiel Informationen wie der docstring
der Funktion, __name__
und die Parameterliste. Sehen Sie sich zunächst das folgende Beispiel an:
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # The output result is: # bar is running # i am bar # _deco
Es ist ersichtlich, dass der Funktionsname zu _deco
anstelle des ursprünglichen bar
geworden ist. Wenn wir Reflexionsfunktionen verwenden, verursacht diese Situation Probleme. Um dieses Problem zu lösen, können wir functools.wraps
einführen. Der Beispielcode mit functools.wraps
lautet wie folgt:
import functools def use_logging(func): @functools.wraps(func) def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # The output result is: # bar is running # i am bar # bar
Wie aus den obigen Ergebnissen hervorgeht, erhalten wir nach der Verwendung von functools.wraps
das erwartete Ergebnis und behalten erfolgreich den Namen der ursprünglichen Funktion bei.
(VI) Erreichen der Anpassungsfähigkeit für Dekorateure mit und ohne Parameter
import functools def use_logging(arg): if callable(arg): # Determine whether the passed parameter is a function. The decorator without parameters calls this branch. @functools.wraps(arg) def _deco(*args, **kwargs): print("%s is running" % arg.__name__) arg(*args, **kwargs) return _deco else: # The decorator with parameters calls this branch. def _deco(func): @functools.wraps(func) def __deco(*args, **kwargs): if arg == "warn": print "warn%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging("warn") # @use_logging def bar(): print('i am bar') print(bar.__name__) bar()
IV. Klassendekorateure
Die Verwendung von Klassendekorateuren kann nicht nur den Effekt von Dekorateuren mit Parametern erzielen, sondern auch die Implementierungsmethode ist eleganter und prägnanter. Gleichzeitig kann sie durch Vererbung flexibel erweitert werden.
(I) Klassendekorateure
class loging(object): def __init__(self, level="warn"): self.level = level def __call__(self, func): @functools.wraps(func) def _deco(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return _deco def notify(self, func): # logit only logs and does nothing else print "%s is running" % func.__name__ @loging(level="warn") # Execute the __call__ method def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
(II) Vererben und Erweitern von Klassendekorateuren
class email_loging(Loging): ''' An implementation version of loging that can send an email to the administrator when the function is called ''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_loging, self).__init__(*args, **kwargs) def notify(self, func): # Send an email to self.email print "%s is running" % func.__name__ print "sending email to %s" % self.email @email_loging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
Im obigen Code erbt die Klasse email_loging
von der Klasse Loging
. Durch diese Vererbungsbeziehung können wir neue Funktionen hinzufügen, ohne die Kernlogik der ursprünglichen Klasse zu ändern, z. B. das Senden einer E-Mail an den Administrator, wenn die Funktion aufgerufen wird. Dies spiegelt die Vorteile von Klassendekorateuren bei der Codeerweiterung und -wiederverwendung voll wider.
Leapcell: Die Serverless-Plattform der nächsten Generation für Python-App-Hosting
Abschließend möchte ich die beste Plattform für die Bereitstellung von Python-Diensten empfehlen: Leapcell
1. Unterstützung mehrerer Sprachen
- Entwickeln Sie mit JavaScript, Python, Go oder Rust.
2. Stellen Sie unbegrenzt viele Projekte kostenlos bereit
- Zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
3. Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
4. Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollständig automatisierte CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für umsetzbare Erkenntnisse.
5. Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Null Betriebsaufwand – konzentrieren Sie sich einfach aufs Bauen.
Erfahren Sie mehr in der Dokumentation!
Leapcell Twitter: https://x.com/LeapcellHQ