Leçons du module (4/4)
Décorateurs : fonctions modifiant des fonctions
Un décorateur (decorator) est une fonction qui prend une autre fonction et en renvoie une version "décorée". C'est un moyen puissant d'ajouter du comportement (journalisation, chronométrage, mise en cache, tentatives, validation) sans modifier le corps de la fonction d'origine.
Les fonctions comme valeurs
En Python, les fonctions sont des objets : vous pouvez les passer, les renvoyer, les assigner.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Un décorateur minimal
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
# 5Syntaxe de @decorator
L'affectation somma = log(somma) s'écrit de manière plus élégante avec @ :
@log
def somma(a, b):
return a + bC'est exactement la même chose. @log au-dessus de la définition équivaut à : "dès que cette fonction est définie, remplace-la par log(fonction)".
*args, **kwargs : accepter n'importe quelle signature
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args collecte les arguments positionnels, **kwargs les arguments par mot-clé. Ils vous permettent d'écrire des wrappers qui fonctionnent avec n'importe quelle fonction.
functools.wraps : préserver les métadonnées
Sans wraps, la fonction décorée perd __name__, __doc__, les types-hints :
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperExemple : @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()Décorateurs de la 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— mémorise les résultats d'une fonction pure.@property— expose une méthode sous forme d'attribut (vu dans le module OOP).@staticmethod,@classmethod— modifient la façon dontselfest transmis.
Décorateurs avec arguments
Les décorateurs peuvent également accepter des arguments. Pour implémenter cela, vous devez écrire trois niveaux de fonctions imbriquées : la fonction la plus externe reçoit les arguments du décorateur, la fonction intermédiaire reçoit la fonction décorée, et la fonction la plus interne (wrapper) reçoit les arguments réels passés à la fonction décorée lors des appels.
À vous de jouer
Écrivez un décorateur `double_result` qui appelle la fonction décorée et renvoie son résultat * 2. Appliquez-le à une fonction `f(x)` qui renvoie x + 1. Évaluez `f(10)`.
Afficher l'indice
Le wrapper appelle func et renvoie le résultat multiplié par 2.
Solution disponible après 3 tentatives
Exercice de révision
Utilisez functools.lru_cache pour accélérer une fonction récursive fib(n). Calculez fib(30) et assignez-le à `r`. Évaluez `r`.
Afficher l'indice
@lru_cache(maxsize=None) au-dessus du def.
Solution disponible après 3 tentatives
Défi supplémentaire
Définissez un décorateur `double_result` qui double (multiplie par 2) la valeur de retour de la fonction décorée. Appliquez le décorateur à la fonction `def sum_nums(a, b): return a + b`. Enfin, évaluez `sum_nums(3, 4)`.
Afficher l'indice
Le wrapper appelle func(*args, **kwargs) et multiplie le résultat par 2.
Solution disponible après 3 tentatives