Decorateurs verschenen in Python als syntactische suiker met versie 2.4, om het werken met functies van hogere orde te vergemakkelijken - dat wil zeggen functies die andere functies aannemen of retourneren. De evolutie van benaderingen om de functionaliteit van functies uit te breiden leidde tot het formaat van beknopte en expressieve middelen - annotaties via @decorator.
In grote projecten is het vaak nodig om functies te modificeren of te omhullen met extra logica: logging, toegangscontrole, caching, het meten van de uitvoeringstijd. Zonder decorateurs moest men handmatig omhulde functies aanroepen, wat de code opblies.
Decorateurs stellen je in staat om herhaaldelijk voorkomende aspecten naar aparte omhulsels te verplaatsen, waardoor de leesbaarheid en herbruikbaarheid van de code toeneemt. Met behulp van @decorator kan elegant functionaliteit aan functies en methoden worden toegevoegd:
Voorbeeldcode:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Tijd verstreken: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def langzame_functie(): time.sleep(0.5) langzame_functie() # Zal weergeven hoe lang de uitvoering duurde
Belangrijkste kenmerken:
Kunnen decorateurs de handtekening van de omhulde functie wijzigen?
Vaak wordt ten onrechte aangenomen dat de handtekening van de functie door een decorateur niet verandert. In werkelijkheid verdwijnt de metainformatie zonder het gebruik van de functools.wraps module, wat onverwachte fouten kan veroorzaken in autodocumentatie of introspectie.
Voorbeeldcode:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
Kunnen decorateurs geparameteriseerd worden?
Vaak zeggen mensen dat dit niet kan. In feite is het mogelijk om een geparameteriseerde decorateur te maken via een extra niveau van genesting - een functie die een decorateur retourneert.
Voorbeeldcode:
def herhaal(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @herhaal(3) def hallo(): print("Hallo!")
Kan je meerdere decorateurs op één functie toepassen? Wat zal de volgorde van uitvoering zijn?
Vaak wordt ten onrechte gedacht dat de volgorde niet belangrijk is of dat deze samenvalt met de volgorde waarin de decorateurs in de code staan. In werkelijkheid wordt eerst de onderste decorateur toegepast, dan de volgende, en zo verder omhoog.
Voorbeeldcode:
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
Logging van de uitvoeringstijd is handmatig toegevoegd aan 10 functies door middel van kopiëren en plakken.
Voordelen:
Nadelen:
Alle logica voor timing is in een decorateur geplaatst, alle functies waar deze metric nodig is, zijn omhuld met de decorateur @timing_decorator.
Voordelen:
Nadelen:
functools.wraps, als je niet voorzichtig bent;