Introduction
Les décorateurs sont l’une des fonctionnalités les plus puissantes — et souvent déroutantes — de Python. À première vue, la syntaxe @decorator peut sembler magique. Mais une fois que l’on comprend comment fonctionnent les décorateurs, ils deviennent un outil élégant pour écrire du code plus propre et plus réutilisable.
Table des matières
- Introduction
- Qu’est-ce qu’un décorateur en Python ?
- Un exemple de fonction simple
- Écrire votre premier décorateur
- La syntaxe @decorator
- Décorateurs avec arguments
- Préserver les métadonnées de la fonction (functools.wraps)
- Décorateurs avec paramètres (avancé)
- Cas d’usage courants dans le monde réel
- Décorateurs intégrés que vous utilisez déjà
- Quand faut-il utiliser des décorateurs ?
- Références
Qu’est-ce qu’un décorateur en Python ?
Un décorateur est une fonction qui :
- Prend une autre fonction en entrée
- Étend ou modifie son comportement
- Retourne une nouvelle fonction
Idée clé : les décorateurs permettent d’ajouter des fonctionnalités sans modifier le code de la fonction originale.
En Python, les fonctions sont des objets de première classe, ce qui signifie que :
- Elles peuvent être passées en argument
- Elles peuvent être retournées par d’autres fonctions
- Elles peuvent être assignées à des variables
Les décorateurs reposent entièrement sur ce concept.
Un exemple de fonction simple
Commençons par une fonction basique :
1 2 | def greet(): print("Hello!") |
Appel de la fonction :
1 | greet() |
Sortie :
1 | Hello! |
Supposons maintenant que nous voulions :
- Afficher quelque chose avant et après l’exécution de
greet()
Nous pourrions modifier la fonction — mais cette approche ne passe pas à l’échelle.
C’est précisément là que les décorateurs deviennent utiles.
Écrire votre premier décorateur
Voici un décorateur simple :
1 2 3 4 5 6 | def my_decorator(func): def wrapper(): print("Before the function runs") func() print("After the function runs") return wrapper |
Décomposons-le :
funcest la fonction décoréewrapper()ajoute un comportement supplémentairefunc()appelle la fonction originale- Le décorateur retourne la nouvelle fonction
Application manuelle :
1 2 3 4 5 | def greet(): print("Hello!") greet = my_decorator(greet) greet() |
Sortie :
1 2 3 | Before the function runs Hello! After the function runs |
La syntaxe @decorator
Python fournit une syntaxe plus propre avec @ :
1 2 3 | @my_decorator def greet(): print("Hello!") |
Ceci est équivalent à :
1 | greet = my_decorator(greet) |
Beaucoup plus lisible — et plus élégant.
Décorateurs avec arguments
La plupart des fonctions réelles acceptent des paramètres. Mettons à jour notre décorateur :
1 2 3 4 5 6 7 | def my_decorator(func): def wrapper(*args, **kwargs): print("Before function") result = func(*args, **kwargs) print("After function") return result return wrapper |
Il fonctionne maintenant avec n’importe quelle fonction :
1 2 3 4 5 | @my_decorator def add(a, b): return a + b print(add(3, 5)) |
Sortie :
1 2 3 | Before function After function 8 |
Préserver les métadonnées de la fonction (functools.wraps)
Sans précaution particulière, les décorateurs peuvent écraser :
- Le nom de la fonction
- La docstring
- Les annotations
Exemple du problème :
1 | print(add.__name__) # wrapper |
Solution avec functools.wraps :
1 2 3 4 5 6 7 | from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper |
Les métadonnées sont maintenant préservées :
1 | print(add.__name__) # add |
Décorateurs avec paramètres (avancé)
Parfois, les décorateurs eux-mêmes ont besoin d’arguments :
1 2 3 4 5 6 7 | def repeat(n): def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator |
Utilisation :
1 2 3 4 5 | @repeat(3) def say_hi(): print("Hi!") say_hi() |
Sortie :
1 2 3 | Hi! Hi! Hi! |
Cas d’usage courants dans le monde réel
Journalisation (logging)
1 2 3 4 5 | def log(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper |
Mesure du temps d’exécution
1 2 3 4 5 6 7 8 9 10 | import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.4f}s") return result return wrapper |
Authentification / permissions
Les décorateurs sont largement utilisés dans des frameworks web comme Flask et Django.
1 2 3 4 5 6 | def require_login(func): def wrapper(user): if not user.is_authenticated: raise PermissionError("Login required") return func(user) return wrapper |
Décorateurs intégrés que vous utilisez déjà
Python inclut de nombreux décorateurs intégrés :
@staticmethod@classmethod@property@dataclass@lru_cache
Décorateur @lru_cache en Python
Exemple :
1 2 3 4 5 6 7 | from functools import lru_cache @lru_cache def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) |
Décorateur @classmethod en Python
Exemple d’utilisation du décorateur @classmethod en Python
Exemple de base
1 2 3 4 5 6 | class MyClass: value = 10 @classmethod def show_value(cls): return cls.value |
Comment ça fonctionne
1 | print(MyClass.show_value()) # 10 |
@classmethodreçoit la classe elle-même comme premier argument (cls)- Il n’est pas nécessaire de créer une instance
- Il peut accéder aux variables de classe (
cls.value)
Comparaison avec une méthode d’instance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class MyClass: value = 10 def instance_method(self): return self.value @classmethod def class_method(cls): return cls.value obj = MyClass() print(obj.instance_method()) # 10 print(MyClass.class_method()) # 10 |
self→ instancecls→ classe
Exemple pratique : constructeur alternatif
C’est le cas d’usage le plus courant en pratique.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class FirePixel: def __init__(self, lat, lon, frp): self.lat = lat self.lon = lon self.frp = frp @classmethod def from_dict(cls, data): return cls( lat=data["latitude"], lon=data["longitude"], frp=data["frp"] ) |
Utilisation
1 2 3 4 5 | d = {"latitude": 34.2, "longitude": -118.4, "frp": 56.7} pixel = FirePixel.from_dict(d) print(pixel.lat, pixel.lon, pixel.frp) |
Pourquoi utiliser @classmethod ici ?
- Crée une instance sans coder en dur le nom de la classe
- Fonctionne correctement si la classe est sous-classée
Avantage avec l’héritage (sous-classement)
1 2 3 4 5 | class VIIRSPixel(FirePixel): pass p = VIIRSPixel.from_dict(d) print(type(p)) # <class '__main__.VIIRSPixel'> |
Si from_dict était une @staticmethod, cela ne fonctionnerait pas correctement.
Quand utiliser @classmethod
Utilisez-le lorsque :
- Vous avez besoin d’accéder à la classe, et non à une instance
- Vous voulez définir des méthodes de fabrique / constructeurs alternatifs
- Vous souhaitez un code qui respecte l’héritage
Quand ne PAS l’utiliser
- Si vous avez seulement besoin des données de l’instance → utilisez une méthode normale
- Si vous n’avez pas besoin de
cls→ utilisez@staticmethod
Quand faut-il utiliser des décorateurs ?
Utilisez des décorateurs lorsque vous voulez :
- Ajouter un comportement transversal (logging, timing, cache)
- Éviter la duplication de code
- Garder la logique métier claire
- Appliquer un comportement de manière cohérente à plusieurs fonctions
Évitez les décorateurs lorsque :
- La logique est très spécifique à une seule fonction
- La lisibilité du code en souffrirait
Références
| Liens | Site |
|---|---|
| Python Glossary – Decorator | python.org |
| Python Language Reference – Function Definitions | python.org |
| Python Standard Library – functools.wraps | python.org |
| Python Standard Library – functools.lru_cache | python.org |
| PEP 318 – Decorators for Functions and Methods | python.org (PEP) |
