Modulelessen (4/4)
Decorateurs: functies die functies wijzigen
Een decorateur is een functie die een andere functie aanneemt en a retourneert "versierde" versie ervan. Het is een krachtige manier om gedrag toe te voegen (loggen, timing, caching, opnieuw proberen, validatie) zonder de hoofdtekst van het bestand aan te raken de oorspronkelijke functie.
Functioneert als waarden
In Python zijn functies objecten: je kunt ze doorgeven, retourneren en toewijzen.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Een minimale decorateur
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
# 5@decorator syntaxis
De opdracht somma = log(somma) is eleganter geschreven met @:
@log
def somma(a, b):
return a + bHet is precies hetzelfde. @log boven de definitie is gelijk aan:
"Zodra deze functie is gedefinieerd, vervangt u deze door log(function)".
*args, **kwargs: accepteer elke handtekening
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args verzamelt positionele argumenten, **kwargs trefwoordargumenten. Ze lieten
je schrijft wrappers die met elke functie werken.
functools.wraps: bewaar metadata
Zonder wraps verliest de gedecoreerde functie __name__, __doc__, hints:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperVoorbeeld: @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()Decorateurs uit de 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— onthoudt de resultaten van een zuivere functie.@property— toont een methode als attribuut (te zien in de OOP-module).@staticmethod,@classmethod— wijzig hoeselfwordt doorgegeven.
Decorateurs met argumenten
Decorateurs kunnen ook argumenten accepteren. Om dit te implementeren moet je drie niveaus van geneste functies schrijven: de buitenste functie ontvangt de argumenten van de decorateur, de middelste functie ontvangt de versierde functie en de binnenste functie (wrapper) ontvangt de feitelijke argumenten die tijdens aanroepen aan de versierde functie worden doorgegeven.
Probeer het zelf
Schrijf een decorateur `double_result` die de gedecoreerde functie aanroept en het resultaat * retourneert. 2. Pas het toe op een functie `f(x)` die x + 1 retourneert. Evalueer `f(10)`.
Toon hint
De wrapper roept func aan en retourneert het resultaat vermenigvuldigd met 2.
Oplossing beschikbaar na 3 pogingen
Oefening beoordelen
Gebruik functools.lru_cache om een recursieve fib(n)-functie te versnellen. Bereken fib(30) en wijs deze toe aan `r`. Evalueer `r`.
Toon hint
@lru_cache(maxsize=None) boven de def.
Oplossing beschikbaar na 3 pogingen
Extra uitdaging
Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.
Toon hint
De wrapper roept func(*args, **kwargs) aan en vermenigvuldigt het resultaat met 2.
Oplossing beschikbaar na 3 pogingen