-
Les décorateurs dans Python
Les décorateurs (wrappers) permet de modifier / étendre le comportement d’une fonction / classe sans en changer le code source.
Fonctionnement de base
Un décorateur est une fonction qui prend une fonction en paramètre et retourne une nouvelle fonction.
def mon_decorateur(fonction_a_decorer): def fonction_modifiee(): print("Avant l'appel de la fonction") fonction_a_decorer() print("Après l'appel de la fonction") return fonction_modifiee @mon_decorateur def ma_fonction(): print("Fonction originale") ma_fonction()
Sortie:
Avant l'appel de la fonction Fonction originale Après l'appel de la fonction
Décorateurs avec arguments
Les décorateurs peuvent aussi gérer des fonctions avec arguments:
def decorateur_avec_args(fonction): def wrapper(*args, **kwargs): print("Arguments reçus:", args, kwargs) return fonction(*args, **kwargs) return wrapper @decorateur_avec_args def addition(a, b): return a + b print(addition(3, 5))
Décorateurs avec paramètres
Vous pouvez aussi créer des décorateurs qui acceptent leurs propres paramètres:
def repeteur(n): def decorateur(fonction): def wrapper(*args, **kwargs): for _ in range(n): result = fonction(*args, **kwargs) return result return wrapper return decorateur @repeteur(3) def dire_bonjour(): print("Bonjour !") dire_bonjour()
Cas d’utilisation courants
Les décorateurs sont souvent utilisés pour:
Logging/tracing
Contrôle d’accès/autorisation
Temporisation (mesure du temps d’exécution)
Mise en cache (mémoization)
Validation des entrées/sorties
Exemple de décorateur pour mesurer le temps:
import time def chronometre(fonction): def wrapper(*args, **kwargs): debut = time.time() result = fonction(*args, **kwargs) fin = time.time() print(f"Temps d'exécution: {fin - debut:.4f} secondes") return result return wrapper @chronometre def attendre(secondes): time.sleep(secondes) attendre(1)
Décorateurs de classe
Vous pouvez aussi décorer des classes:
def decorateur_classe(cls): class NouvelleClasse(cls): def nouvelle_methode(self): print("Méthode ajoutée par le décorateur") return NouvelleClasse @decorateur_classe class MaClasse: def methode_existante(self): print("Méthode existante") obj = MaClasse() obj.methode_existante() obj.nouvelle_methode()
Important: préserver les métadonnées
L’utilisation de décorateurs peut masquer les métadonnées de la fonction originale. Pour les préserver, utilisez
functools.wraps
:from functools import wraps def bon_decorateur(f): @wraps(f) def wrapper(*args, **kwargs): """Docstring du wrapper""" return f(*args, **kwargs) return wrapper @bon_decorateur def exemple(): """Docstring originale""" pass print(exemple.__name__) # Affiche 'exemple' au lieu de 'wrapper' print(exemple.__doc__) # Affiche la docstring originale
Les décorateurs sont un outil extrêmement puissant en Python qui permet d’ajouter des comportements transversaux de manière élégante et réutilisable.
donne-moi 3 exemples concrets où les décorateurs ont un intéret
Exemples
Logging et Debugging
Tracer l’exécution de certaines fonctions (qui les appelle, avec quels arguments, combien de temps elles prennent, etc.) sans modifier leur code.
import time from functools import wraps def logger(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Appel de {func.__name__} avec args={args}, kwargs={kwargs}") start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"{func.__name__} terminé en {end_time - start_time:.2f}s") return result return wrapper @logger def calcul_complexe(x, y): time.sleep(1) # Simulation d'un calcul long return x * y print(calcul_complexe(3, 4))
Sortie :
Appel de calcul_complexe avec args=(3, 4), kwargs={} calcul_complexe terminé en 1.00s 12
Intérêt :
→ Réutilisable sur n’importe quelle fonction.
→ Pas de modification du code original.
→ Centralise la logique de logging.Contrôle d’accès (Authentification)
Restreindre l’accès à certaines fonctions uniquement aux utilisateurs autorisés.
def require_auth(func): @wraps(func) def wrapper(user, *args, **kwargs): if not user.get("is_authenticated", False): raise PermissionError("Accès refusé !") return func(user, *args, **kwargs) return wrapper @require_auth def acces_restreint(user): return f"Bienvenue {user['name']} !" # Test user_valide = {"name": "Alice", "is_authenticated": True} user_invalide = {"name": "Bob", "is_authenticated": False} print(acces_restreint(user_valide)) # OK print(acces_restreint(user_invalide)) # Lève une PermissionError
Intérêt :
→ Évite la duplication de code (pas besoin de vérifier l’authentification dans chaque fonction).
→ Facile à désactiver/en modifiant juste le décorateur.Mise en cache (Mémoization)
Problème : Une fonction coûteuse en calcul est appelée plusieurs fois avec les mêmes arguments (ex: Fibonacci, appel API, etc.).
from functools import wraps def memoize(func): cache = {} @wraps(func) def wrapper(*args): if args not in cache: cache[args] = func(*args) return cache[args] return wrapper @memoize def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2) print(fibonacci(50)) # Calcul rapide grâce au cache
Intérêt :
→ Améliore drastiquement les performances pour les fonctions récursives ou coûteuses.
→ Cache automatique sans modifier la logique de la fonction.Combiner plusieurs décorateurs pour une fonction !
@logger @require_auth @memoize def fonction_avancee(user, x): # ...