Einführung
In diesem Lab lernen Sie Python Decorators (Dekoratoren) kennen, ein mächtiges Feature, das eine prägnante und wiederverwendbare Methode bietet, die Funktionalität einer Funktion oder Klasse zu erweitern, ohne sie dauerhaft zu verändern.
Sie beginnen damit, einen einfachen Decorator zu erstellen, lernen dann, wie man Funktionsparameter behandelt, Best Practices mithilfe von functools.wraps anwendet, Decorators erstellt, die eigene Argumente akzeptieren, und schließlich Decorators mithilfe von Klassen implementiert, um Zustände (State) zu verwalten.
Minimalistischer Decorator (Bare Bone Decorator)
In diesem Schritt lernen Sie die grundlegende Struktur eines Python-Dekorators kennen. Ein Dekorator ist in seiner einfachsten Form eine Funktion, die eine andere Funktion als Argument entgegennimmt, ihr zusätzliche Funktionalität hinzufügt und dann eine weitere Funktion zurückgibt.
Hinweis: Wenn Sie mit der Verwendung von WebIDE (ähnlich VS Code) nicht vertraut sind, lernen Sie dies zuerst in diesem Lab.
Öffnen Sie zunächst die Datei step1.py im Dateiexplorer auf der linken Seite.
Kopieren Sie nun den folgenden Code in die Datei step1.py. Dieser Code definiert einen einfachen Dekorator und wendet ihn auf eine Funktion an.
## Decorator: a function that takes another function and returns a wrapper
def your_decorator(func):
def wrapper():
## Do stuff before func...
print("Before func!")
func() ## Call the original function
## Do stuff after func...
print("After func!")
return wrapper ## Return the wrapper function
## @your_decorator is syntactic sugar for: foo = your_decorator(foo)
@your_decorator
def foo():
print("Hello World!")
foo() ## Calls wrapper, which calls foo with extra behavior
Lassen Sie uns den Code aufschlüsseln:
your_decorator(func): Dies ist die Dekoratorfunktion. Sie nimmt ein Argument entgegen,func, die Funktion, die dekoriert werden soll.wrapper(): Dies ist eine innere Funktion, die die ursprüngliche Funktion „umhüllt“ (wraps). Sie enthält das neue Verhalten, das Sie hinzufügen möchten. Hier gibt sie Nachrichten vor und nach dem Aufruf der ursprünglichen Funktion aus.@your_decorator: Dies ist der syntaktische Zucker (syntactic sugar) für das Anwenden des Dekorators. Es ist äquivalent dazu, nach der Funktionsdefinitionfoo = your_decorator(foo)zu schreiben.
Führen Sie nun das Skript im Terminal aus, um den Dekorator in Aktion zu sehen.
python3 step1.py
Sie sollten die folgende Ausgabe sehen, die zeigt, dass die wrapper-Funktion ausgeführt wurde und Verhalten um die ursprüngliche foo-Funktion herum hinzugefügt hat.
Before func!
Hello World!
After func!
Decorator für eine Funktion mit Parametern
In diesem Schritt erweitern Sie Ihren Decorator so, dass er mit Funktionen funktioniert, die Argumente akzeptieren. Eine reale Funktion hat oft Parameter, und unser Decorator muss in der Lage sein, diese korrekt zu behandeln.
Dazu können Sie *args und **kwargs in der Signatur der Wrapper-Funktion verwenden. Dies ermöglicht es dem Wrapper, eine beliebige Anzahl von Positions- und Schlüsselwortargumenten (Positional and Keyword Arguments) entgegenzunehmen und an die ursprüngliche Funktion weiterzuleiten.
Öffnen Sie die Datei step2.py und fügen Sie den folgenden Code hinzu:
## Decorator that works with functions that have parameters
def your_decorator(func):
def wrapper(*args,**kwargs): ## Accept any arguments
## Do stuff before func...
print("Before func!")
func(*args,**kwargs) ## Pass arguments to original function
## Do stuff after func...
print("After func!")
return wrapper
@your_decorator
def foo(bar):
print("My name is " + bar)
foo("Jack") ## Arguments are passed through wrapper
In diesem Code wird die wrapper-Funktion mit *args und **kwargs definiert. Wenn foo("Jack") aufgerufen wird, wird das Argument "Jack" von *args im wrapper erfasst und dann an die ursprüngliche func weitergegeben, wenn func(*args, **kwargs) ausgeführt wird.
Führen Sie das Skript aus, um das Ergebnis zu sehen.
python3 step2.py
Die Ausgabe zeigt, dass das Argument erfolgreich durch den Decorator an die ursprüngliche Funktion weitergegeben wurde.
Before func!
My name is Jack
After func!
Vorlage für einen Basis-Decorator
In diesem Schritt entdecken Sie die Best-Practice-Vorlage (Best-Practice Template) zur Erstellung robuster Decorators. Obwohl die vorherigen Decorators funktionierten, hatten sie einen Nebeneffekt: Sie verloren die Metadaten der ursprünglichen Funktion (wie deren Name __name__ und Docstring __doc__).
Um dies zu beheben, stellt das functools-Modul von Python einen Hilfs-Decorator namens wraps bereit. Darüber hinaus sollte ein guter Decorator in der Lage sein, Funktionen zu verarbeiten, die Werte zurückgeben.
Diese Vorlage ist für die meisten Anwendungsfälle von Decorators nützlich. Sie ist gültig für Funktionen mit oder ohne Parameter und mit oder ohne Rückgabewert.
Öffnen Sie die Datei step3.py und fügen Sie den folgenden Code hinzu.
import functools
## Best practice decorator template: preserves function metadata and return value
def your_decorator(func):
@functools.wraps(func) ## Preserves function name, docstring, etc.
def wrapper(*args,**kwargs):
print("Before function execution...")
result = func(*args,**kwargs) ## Call function and capture return value
print("After function execution...")
return result ## Return the original function's return value
return wrapper
@your_decorator
def add(a, b):
"""This function adds two numbers."""
return a + b
sum_result = add(5, 3)
print(f"Function name: {add.__name__}")
print(f"Docstring: {add.__doc__}")
print(f"Result: {sum_result}")
Wesentliche Verbesserungen in dieser Vorlage:
import functools: Importiert das notwendige Modul.@functools.wraps(func): Dies ist ein Decorator, der auf diewrapper-Funktion angewendet wird. Er kopiert die Metadaten von der ursprünglichenfunc(wie__name__und__doc__) auf diewrapper-Funktion.result = func(*args, **kwargs): Der Rückgabewert der ursprünglichen Funktion wird in der Variablenresulterfasst.return result: Die Wrapper-Funktion gibt das erfasste Ergebnis zurück, sodass sich die dekorierte Funktion wie erwartet verhält.
Führen Sie das Skript aus:
python3 step3.py
Die Ausgabe demonstriert, dass der Name und der Docstring der Funktion erhalten bleiben und der Rückgabewert korrekt behandelt wird.
Before function execution...
After function execution...
Function name: add
Docstring: This function adds two numbers.
Result: 8
Decorator mit Parametern
In diesem Schritt lernen Sie, wie Sie Decorators erstellen, die mit eigenen Parametern angepasst werden können. Dies ermöglicht eine flexiblere und konfigurierbarere Verwendung von Decorators, beispielsweise als @your_decorator(arg='value').
Um dies zu erreichen, müssen Sie eine „Decorator-Fabrik“ (Decorator Factory) erstellen – eine Funktion, die Argumente entgegennimmt und einen Decorator zurückgibt. Dies fügt unserer Struktur eine zusätzliche Verschachtelungsebene hinzu.
Erstellen wir einen Decorator, der einen prefix-String entgegennimmt und diesen vor und nach dem Funktionsaufruf ausgibt. Öffnen Sie step4.py und fügen Sie den folgenden Code hinzu:
import functools
## Decorator factory: returns a decorator based on parameters
def prefix_decorator(prefix):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
## Use the 'prefix' argument from the factory
print(f"{prefix} Before function call")
result = func(*args, **kwargs)
print(f"{prefix} After function call")
return result
return wrapper
return decorator ## Return the actual decorator function
## Using the decorator with a parameter
@prefix_decorator(prefix="LOG:")
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
So funktioniert es:
- Zuerst wird
prefix_decorator(prefix="LOG:")aufgerufen. Dies ist die Fabrikfunktion. - Sie gibt die
decorator-Funktion zurück, die nun über einen Closure Zugriff auf dieprefix-Variable ("LOG:") hat. - Dieser zurückgegebene
decoratorwird dann aufsay_helloangewendet und dekoriert diese.
Führen Sie das Skript aus, um zu sehen, wie der Parameter verwendet wird.
python3 step4.py
Die Ausgabe zeigt das Präfix "LOG:", das als Argument an den Decorator übergeben wurde.
LOG: Before function call
Hello, Alice!
LOG: After function call
Klassenbasierte Decorators
In diesem Schritt werden Sie untersuchen, wie Decorators mithilfe von Klassen implementiert werden. Dieser Ansatz ist besonders nützlich, um Zustände (State) zwischen Funktionsaufrufen beizubehalten. Wir behandeln zwei Muster: das Dekorieren einer Methode innerhalb einer Klasse und das Definieren eines Decorators als Klasse selbst.
Öffnen Sie die Datei step5.py, um zu beginnen.
Muster 1: Dekorieren einer Klassenmethode
Sie können einen Decorator als Methode innerhalb einer Klasse definieren und ihn auf andere Methoden derselben Klasse anwenden. Dies ist nützlich, um spezifische Verhaltensweisen zu erstellen, die an den Kontext der Klasse gebunden sind.
Fügen Sie den folgenden Code zu step5.py hinzu. Dieses Beispiel definiert einen Decorator zur Behandlung von Ausnahmen (Exceptions) für eine Klassenmethode.
## Pattern 1: Decorating a method within a class
class DecorateMyMethod:
## Static method decorator for methods with only 'self' parameter
def decorator_for_class_method_with_no_args(method):
def wrapper_for_class_method(self): ## Only takes self
try:
return method(self) ## Call original method
except Exception as e:
print(f"\nWARNING: An exception occurred: {e}\n")
return wrapper_for_class_method
def __init__(self,succeed:bool):
self.succeed = succeed
@decorator_for_class_method_with_no_args
def class_action(self):
if self.succeed:
print("You succeeded by choice.")
else:
raise Exception("Epic fail of your own creation.")
print("--- Testing Pattern 1 ---")
test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
test_fail = DecorateMyMethod(False)
test_fail.class_action()
print("-" * 25)
Muster 2: Decorator als Klasse
Ein häufigeres und leistungsfähigeres Muster ist die Definition des Decorators selbst als Klasse. Dies ist ideal, wenn der Decorator einen Zustand beibehalten muss. Durch die Implementierung der Methoden __init__() und __call__() können Sie eine Instanz einer Klasse dazu bringen, sich wie eine Funktion zu verhalten.
Erstellen wir einen Decorator, der zählt, wie oft eine Funktion aufgerufen wurde. Fügen Sie den folgenden Code am Ende von step5.py hinzu.
## Pattern 2: Class-based decorator for maintaining state
class CountCallNumber:
def __init__(self, func):
self.func = func ## Store the function to decorate
self.call_number = 0 ## Initialize call counter
def __call__(self, *args, **kwargs): ## Makes instance callable
self.call_number += 1 ## Increment counter
print("This is execution number " + str(self.call_number))
return self.func(*args, **kwargs) ## Call original function
@CountCallNumber
def say_hi(name):
print("Hi! My name is " + name)
print("\n--- Testing Pattern 2 ---")
say_hi("Jack")
say_hi("James")
__init__(self, func): Dies ist der Konstruktor. Er wird aufgerufen, wenn der Decorator angewendet wird (@CountCallNumber). Er empfängt die zu dekorierende Funktion (say_hi) und speichert sie.__call__(self, *args, **kwargs): Diese Methode macht die Klasseninstanz aufrufbar. Wenn Sie die dekorierte Funktion aufrufen (say_hi("Jack")), rufen Sie tatsächlich diese__call__-Methode auf. Sie erhöht den Zähler und ruft dann die ursprünglich gespeicherte Funktion auf.
Führen Sie nun das gesamte Skript aus.
python3 step5.py
Sie sehen die Ausgabe für beide Muster. Beachten Sie, wie der Aufrufzähler im zweiten Teil inkrementiert wird.
--- Testing Pattern 1 ---
You succeeded by choice.
WARNING: An exception occurred: Epic fail of your own creation.
-------------------------
--- Testing Pattern 2 ---
This is execution number 1
Hi! My name is Jack
This is execution number 2
Hi! My name is James
Zusammenfassung
Herzlichen Glückwunsch zum Abschluss des Labs! Sie haben ein solides Verständnis der Python Decorators und ihrer verschiedenen Formen gewonnen.
In diesem Lab haben Sie gelernt, wie man:
- Einen Basis-Decorator erstellt und die
@-Syntax versteht. - Decorators entwirft, die mit Funktionen arbeiten, die eine beliebige Anzahl von Argumenten mithilfe von
*argsund**kwargsakzeptieren. - Die Best-Practice-Vorlage mit
functools.wrapsverwendet, um Funktionsmetadaten zu erhalten und Rückgabewerte zu verarbeiten. - Decorator-Fabriken erstellt, die es ermöglichen, Ihre Decorators mit Parametern anzupassen.
- Decorators als Klassen implementiert, um Zustände zwischen Funktionsaufrufen beizubehalten, wie beispielsweise bei einem Aufrufzähler.
Decorators sind ein Schlüsselelement für das Schreiben von sauberem, wiederverwendbarem und erweiterbarem Python-Code. Sie können diese Muster nun anwenden, um Ihre eigenen Projekte zu verbessern.



