Lektionen des Moduls (4/4)
Dekoratoren: Funktionen, die Funktionen modifizieren
Ein Dekorator (decorator) ist eine Funktion, die eine andere Funktion entgegennimmt und eine "dekorierte" Version davon zurückgibt. Es ist eine leistungsstarke Methode, um Verhalten (Logging, Zeitmessung, Caching, Retry, Validierung) hinzuzufügen, ohne den Rumpf der ursprünglichen Funktion zu verändern.
Funktionen als Werte
In Python sind Funktionen Objekte: Du kannst sie übergeben, zurückgeben und zuweisen.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Ein minimaler Dekorator
def log(func):
def wrapper(*args, **kwargs):
print(f"chiamo {func.__name__}")
risultato = func(*args, **kwargs)
print(f"ho finito {func.__name__}")
return risultato
return wrapper
def somma(a, b):
return a + b
somma = log(somma) # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5Die @decorator-Syntax
Die Zuweisung somma = log(somma) wird mit @ eleganter geschrieben:
@log
def somma(a, b):
return a + bEs ist genau dasselbe. @log über der Definition entspricht: "Sobald diese Funktion definiert ist, ersetze sie durch log(funktion)".
*args, **kwargs: Jede Signatur akzeptieren
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args sammelt Positionsargumente, **kwargs Keyword-Argumente. Sie ermöglichen es dir, Wrapper zu schreiben, die mit jeder Funktion funktionieren.
functools.wraps: Metadaten beibehalten
Ohne wraps verliert die dekorierte Funktion __name__, __doc__ und Type-Hints:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperBeispiel: @timeit
from functools import wraps
import time
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
risultato = func(*args, **kwargs)
dt = time.perf_counter() - t0
print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
return risultato
return wrapper
@timeit
def somma_grande():
return sum(range(10**6))
somma_grande()Dekoratoren aus der stdlib
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n - 1) + fib(n - 2)
fib(100) # istantaneo grazie alla cache@lru_cache– speichert die Ergebnisse einer reinen Funktion (Memoisation).@property– macht eine Methode als Attribut zugänglich (siehe OOP-Modul).@staticmethod,@classmethod– ändern die Art und Weise, wieselfübergeben wird.
Dekoratoren mit Argumenten
Dekoratoren können auch Argumente entgegennehmen. Um dies zu implementieren, musst du drei Ebenen verschachtelter Funktionen schreiben: Die äußerste Funktion erhält die Argumente des Dekorators, die mittlere Funktion erhält die dekorierte Funktion und die innerste Funktion (wrapper) erhält die tatsächlichen Argumente, die der dekorierten Funktion beim Aufruf übergeben werden.
Probiere es aus
Schreibe einen Dekorator `double_result`, der die dekorierte Funktion aufruft und deren Ergebnis * 2 zurückgibt. Wende ihn auf eine Funktion `f(x)` an, die x + 1 zurückgibt. Evaluiere `f(10)`.
Hinweis anzeigen
Der Wrapper ruft func auf und gibt das mit 2 multiplizierte Ergebnis zurück.
Lösung nach 3 Versuchen verfügbar
Wiederholungsübung
Verwende functools.lru_cache, um eine rekursive fib(n)-Funktion zu beschleunigen. Berechne fib(30) und weise das Ergebnis `r` zu. Evaluiere `r`.
Hinweis anzeigen
@lru_cache(maxsize=None) über dem def.
Lösung nach 3 Versuchen verfügbar
Zusätzliche Herausforderung
Definiere einen Dekorator `double_result`, der den Rückgabewert der dekorierten Funktion verdoppelt (mit 2 multipliziert). Wende den Dekorator auf die Funktion `def sum_nums(a, b): return a + b` an. Evaluiere schließlich `sum_nums(3, 4)`.
Hinweis anzeigen
Der Wrapper ruft func(*args, **kwargs) auf und multipliziert das Ergebnis mit 2.
Lösung nach 3 Versuchen verfügbar