Historie des Themas
Dekoratoren sind eines der mächtigsten Werkzeuge von Python, das es ermöglicht, das Verhalten von Funktionen oder Methoden zu modifizieren. Manchmal muss man nicht nur eine Funktion "einwickeln", sondern den Dekorator mithilfe von Parametern (Argumenten) anpassen. Solche Fälle treten beim Logging, Zeitüberprüfungen, Zugriffsbeschränkungen usw. auf.
Problem
Einfache Dekoratoren akzeptieren nur eine Funktion, die verpackt werden soll. Wenn man dem Dekorator Parameter übergeben möchte, wird die Syntax komplizierter, was oft zu Fehlern führt, insbesondere bei verschachtelten Funktionen und beim Durchreichen von *args/**kwargs.
Lösung
Ein Dekorator mit Parametern wird durch eine Funktion höherer Ordnung realisiert: Zuerst wird die äußere "dekoriende" Funktion mit Argumenten aufgerufen, die den Dekorator erstellt und zurückgibt, und der Dekorator selbst erhält dann die Funktion und gibt das Wrapper zurück:
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 greet(name): print(f"Hallo, {name}!") greet("Python") # Ausgabe: Hallo, Python! (3 Mal)
Wichtige Merkmale:
Warum kann ein Dekorator mit Parametern nicht auf die gleiche Weise implementiert werden wie ein einfacher Dekorator?
Wenn Sie @decorator anwenden, übergibt Python die Funktion als Argument an den Dekorator. Wenn Sie Klammern hinzufügen (@decorator()), ruft Python zuerst die Funktion auf und nur das Ergebnis wird als Dekorator interpretiert.
def deco(func): # einfacher Dekorator: @deco def deco_with_args(arg): # Dekorator mit Argument: @deco_with_args(arg)
Wie unterscheiden sich Dekoratoren mit Argumenten grundsätzlich von Dekoratoren ohne Argumente auf der Aufrufebene?
Dekoratoren ohne Argumente erhalten die Funktion als Eingabe, während Dekoratoren mit Argumenten nicht die Funktion, sondern Parameter erhalten und einen Dekorator zurückgeben.
Wie verwendet man functools.wraps richtig und warum ist das wichtig?
functools.wraps(func) im Wrapper speichert den Namen, die Dokumentationszeile und andere Metadaten der ursprünglichen Funktion, andernfalls gehen all diese Informationen im Wrapper verloren, was Debugging und Introspektion erschwert.
import functools def deco_with_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator
Ein Dekorator wurde ohne Berücksichtigung der Parameter oder mit falscher Anzahl an verschachtelten Funktionen implementiert:
def log(level): def wrapper(func): # Fehler — Wrapper muss tiefer sein print(f"Log: {level}") func() # Funktion wird nicht als Dekorator zurückgegeben return wrapper @log("INFO") def action(): print("Arbeit!")
Vorteile:
Nachteile:
Verwendung von functools.wraps und korrekten verschachtelten Funktionen:
import functools def timer(units): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() if units == 'ms': duration = (end - start) * 1000 else: duration = end - start print(f"Dauer: {duration:.4f} {units}") return result return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()
Vorteile:
Nachteile: