Ein Dekorator erweitert eine Funktion um zusätzliches Verhalten, ohne ihren Code zu ändern. Du schreibst ihn mit @name direkt über die Funktion. Dekoratoren begegnen dir in der Praxis ständig, zum Beispiel in Webframeworks (@app.route) oder bei @cache.
Die Idee
Ein Dekorator ist eine Funktion, die eine Funktion bekommt und eine (meist neue) Funktion zurückgibt:
def laut(funktion):
def verpackt(*args, **kwargs):
ergebnis = funktion(*args, **kwargs)
return ergebnis.upper() + "!"
return verpackt
@laut
def gruss(name):
return f"hallo {name}"
print(gruss("mia"))HALLO MIA!
Die Zeile @laut ist nur eine Kurzschreibweise für gruss = laut(gruss).
Beispiel: Aufrufe zählen und Zeit messen
import time
def stoppe_zeit(funktion):
def verpackt(*args, **kwargs):
start = time.perf_counter()
ergebnis = funktion(*args, **kwargs)
dauer = time.perf_counter() - start
print(f"{funktion.__name__} dauerte {'kurz' if dauer < 1 else 'lang'}")
return ergebnis
return verpackt
@stoppe_zeit
def rechne():
return sum(range(100000))
print(rechne())rechne dauerte kurz 4999950000
functools.wraps
Der verpackte Name und Docstring gehen verloren, wenn man nichts unternimmt. Mit functools.wraps bleiben sie erhalten:
from functools import wraps
def log(funktion):
@wraps(funktion)
def verpackt(*args, **kwargs):
print(f"Aufruf von {funktion.__name__}{args}")
return funktion(*args, **kwargs)
return verpackt
@log
def addiere(a, b):
"""Addiert zwei Zahlen."""
return a + b
print(addiere(2, 3))
print(addiere.__name__, "|", addiere.__doc__)Aufruf von addiere(2, 3) 5 addiere | Addiert zwei Zahlen.
Dekoratoren mit Argumenten
Brauchst du Einstellungen, packst du eine weitere Funktion außen herum:
def wiederhole(n):
def dekorator(funktion):
def verpackt(*args, **kwargs):
for _ in range(n):
funktion(*args, **kwargs)
return verpackt
return dekorator
@wiederhole(3)
def sag(text):
print(text)
sag("Hallo")Hallo Hallo Hallo
Eingebaute Dekoratoren
| Dekorator | Zweck |
|---|---|
@functools.cache | Ergebnisse zwischenspeichern |
@staticmethod | Methode ohne Zugriff auf Objekt oder Klasse |
@classmethod | Methode, die die Klasse bekommt |
@property | Methode wie ein Attribut ansprechen |
@dataclass | Klasse mit automatischem __init__ und mehr |
Die letzten vier lernst du im Abschnitt über Klassen kennen.
Merke
- Ein Dekorator nimmt eine Funktion und gibt eine erweiterte Funktion zurück
@dekoratorüberdefist dasselbe wief = dekorator(f)*args, **kwargsreichen beliebige Argumente durch@functools.wrapserhält Name und Docstring- Für Einstellungen: Dekorator-Fabrik mit einer Funktion außen herum
Aufgabe
Schreibe einen Dekorator nur_positiv, der eine Funktion nur aufruft, wenn alle Argumente größer als 0 sind, und sonst None zurückgibt.