Exploitez les fonctions comme objets de première classe pour écrire des décorateurs robustes, paramétrables et transparents.
Ouvrir cette leçon dans KodokonEn Python, une fonction est un objet comme un autre : vous pouvez l'affecter à une variable, la passer en argument, la retourner. Une fonction d'ordre supérieur prend ou renvoie une fonction - sorted(data, key=...) en est une. Ajoutez les fermetures (closures), où une fonction interne capture les variables de la fonction englobante et les garde vivantes après son retour, et vous avez tous les ingrédients des décorateurs.
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10)) # 20 30Un décorateur est une fonction qui reçoit une fonction et en retourne une autre - généralement un wrapper qui ajoute un comportement autour de l'appel. La syntaxe @timed au-dessus de def slow_sum est un sucre strict pour slow_sum = timed(slow_sum). Le wrapper accepte *args, kwargs pour rester agnostique de la signature, et retourne** le résultat de la fonction décorée - l'oublier est une erreur classique qui transforme tous les retours en None.
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
delay = time.perf_counter() - start
print(f"{func.__name__}: {delay:.4f}s")
return result
return wrapper
@timed
def slow_sum(n):
return sum(range(n))
print(slow_sum(1_000_000))Pour paramétrer un décorateur (@repeat(times=3)), il faut un niveau de plus : une fabrique qui reçoit les paramètres et retourne le décorateur, lequel retourne le wrapper. Trois fonctions imbriquées - c'est le prix de la flexibilité. Si l'état devient complexe, une classe dotée de __call__ est souvent plus lisible. Réservez les décorateurs aux préoccupations transversales : journalisation, cache, retry, contrôle d'accès. Un décorateur qui modifie la logique métier cache l'essentiel à la lecture.
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Bonjour {name}")
greet("Ada")