Dekoratoren wurden in Python mit Version 2.4 als syntaktischer Zucker eingeführt, um die Arbeit mit Funktionen höherer Ordnung zu erleichtern – also solchen, die andere Funktionen akzeptieren oder zurückgeben. Die Evolution der Ansätze zur Erweiterung der Funktionalität von Funktionen führte zu einem Format prägnanter und ausdrucksvoller Mittel – Annotationen durch @decorator.
In großen Projekten ist es oft erforderlich, Funktionen mit einer zusätzlichen Logik zu modifizieren oder zu umhüllen: Protokollierung, Zugriffskontrolle, Caching, Zeitmessung. Ohne Dekoratoren musste man umhüllende Funktionen manuell aufrufen, was den Code aufblähte.
Dekoratoren ermöglichen es, wiederkehrende Aspekte in separate Wrapper auszulagern, wodurch die Lesbarkeit und Wiederverwendbarkeit des Codes erhöht wird. Mit @decorator kann man elegant Funktionalität zu Funktionen und Methoden hinzufügen:
Beispielcode:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Gibt aus, wie lange die Ausführung gedauert hat
Wesentliche Merkmale:
Können Dekoratoren die Signatur der umhüllenden Funktion ändern?
Oft wird fälschlicherweise angenommen, dass die Signatur der Funktion durch den Dekorator nicht geändert wird. Tatsächlich verschwindet ohne die Verwendung des Moduls functools.wraps die Metainformation, was unerwartete Fehler in der Autodokumentation oder bei der Introspektion verursachen kann.
Beispielcode:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
Können Dekoratoren parametrisiert werden?
Oft wird geantwortet, dass dies nicht möglich ist. Tatsächlich kann man einen parametrisierten Dekorator durch eine zusätzliche Ebene der Verschachtelung – eine Funktion, die einen Dekorator zurückgibt – erstellen.
Beispielcode:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
Kann man mehrere Dekoratoren für eine Funktion anwenden? In welcher Reihenfolge wird sie ausgeführt?
Oft wird fälschlicherweise angenommen, dass die Reihenfolge unwichtig ist oder der Reihenfolge der Dekoratoren im Code entspricht. Tatsächlich wird zuerst der unterste Dekorator angewendet, dann der nächste und so weiter nach oben.
Beispielcode:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
Die Protokollierung der Laufzeit wurde manuell für 10 Funktionen hinzugefügt, durch Kopieren und Einfügen.
Vorteile:
Nachteile:
Die gesamte Logik der Zeitmessung wurde in einen Dekorator ausgelagert, alle Funktionen, bei denen diese Metrik erforderlich ist, wurden mit dem Dekorator @timing_decorator umhüllt.
Vorteile:
Nachteile:
functools.wraps, wenn man nicht vorsichtig ist;