Les décorateurs
Les décorateurs sont des outils très puissants et utiles en Python, car ils permettent aux programmeurs de modifier le comportement d'une fonction ou d'une classe. Ils permettent d'envelopper (wrapper) une autre fonction afin d'étendre son comportement, sans la modifier de façon permanente.
@nom_decorateur placée juste avant la définition de la fonction cible.def decorateur(func):
def wrapper(*args, **kwargs):
# comportement avant
resultat = func(*args, **kwargs)
# comportement après
return resultat
return wrapper
@decorateur
def ma_fonction():
...@decorateur juste avant def ma_fonction(): est strictement équivalent à écrire : ma_fonction = decorateur(ma_fonction) après la définition. C'est uniquement une syntaxe plus lisible.Premier exemple — Ajouter un message
L'exemple suivant crée un décorateur qui ajoute automatiquement « Bonjour » avant le résultat de n'importe quelle fonction renvoyant une chaîne.
Exemple
def decorateur(func):
# Fonction interne qui ajoute "Bonjour" avant l'appel
def wrapper(para):
return "Bonjour " + func(para)
return wrapper
@decorateur
def saluer(nom):
return nom
print(saluer("Mostafa")) # -> Bonjour MostafaBonjour Mostafa
Voici ce qui se passe étape par étape :
saluerest passée en argument àdecorateur.decorateurretournewrapper, qui remplacesaluer.- À l'appel de
saluer("Mostafa"), c'estwrapper("Mostafa")qui s'exécute.
Attacher des données à une fonction
Les décorateurs peuvent également être utiles pour attacher des données (ajouter un attribut) directement à une fonction. Cela permet de stocker des métadonnées associées à la fonction.
Exemple — Ajouter un attribut
def add_data(func):
func.donnee = 5 # On attache un attribut directement à la fonction
return func # On retourne la fonction enrichie (pas de wrapper ici)
@add_data
def somme(x, y):
return x + y
print(somme(3, 4)) # 7 — comportement normal
print(somme.donnee) # 5 — attribut attaché par le décorateur7 5
- Appeler
somme(3, 4)renvoie simplement la somme7. - Appeler
somme.donneeretourne5: la fonction a été enrichie paradd_datad'un attribut supplémentaire.
@app.route("/chemin") qui attache l'URL à la fonction de vue.Mesurer le temps d'exécution
Un cas d'usage classique des décorateurs est de mesurer automatiquement le temps d'exécution d'une fonction, sans modifier son code.
*args et **kwargs Pour que le décorateur soit universel (applicable à toute fonction, quels que soient ses paramètres), la fonction interne wrapperaccepte :*args: récupère tous les arguments positionnels sous forme de tuple.**kwargs: récupère tous les arguments nommés sous forme de dictionnaire.
Exemple — Décorateur de chronométrage
import time
def temps_execution(func):
def wrapper(*args, **kwargs):
debut = time.time() # horodatage avant
resultat = func(*args, **kwargs)
fin = time.time() # horodatage après
print(f"Temps d'exécution de '{func.__name__}' : {fin - debut:.6f} s")
return resultat
return wrapper
@temps_execution
def factorielle(num):
f = 1
for i in range(2, num + 1):
f *= i
print(f"factorielle({num}) = {f}")
factorielle(70)factorielle(70) = 119785716699698917960727837... Temps d'exécution de 'factorielle' : 0.000021 s
func.__name__ L'attribut __name__ d'une fonction contient son nom sous forme de chaîne. Il est très utile dans les décorateurs pour afficher ou journaliser le nom de la fonction décorée sans le coder en dur.Décorateurs avec paramètres
Il est possible de passer des paramètres à un décorateur. Pour cela, on ajoute un niveau d'imbrication supplémentaire : une fonction externe reçoit les paramètres et retourne le décorateur réel.
@nom_decorateur(params)
def nom_fonction():
...- Niveau 1 : fonction externe qui reçoit les paramètres du décorateur.
- Niveau 2 : décorateur réel qui reçoit la fonction cible.
- Niveau 3 : fonction
wrapperqui reçoit les arguments de la fonction.
Exemple — Décorateur avec paramètres nommés
def decorateur(*args, **kwargs):
def appliquer(func):
print(f"Année : {args[0]}")
print(f"Site : {kwargs['site']}")
print(f"Ville : {kwargs['ville']}")
return func
return appliquer
@decorateur(2019, site="developpement-informatique.com", ville="Meknès")
def test():
print("Fonction test exécutée")
test()Année : 2019 Site : developpement-informatique.com Ville : Meknès Fonction test exécutée
Exemple — Décorateur de répétition
Un exemple plus pédagogique : un décorateur qui répète l'appel d'une fonction n fois.
def repeter(n):
def decorateur(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorateur
@repeter(3)
def saluer(nom):
print(f"Bonjour {nom} !")
saluer("Mostafa")Bonjour Mostafa ! Bonjour Mostafa ! Bonjour Mostafa !
Mémorisation à l'aide de décorateurs
Le problème avec la récursivité naïve
La récursivité est une technique dans laquelle une fonction s'appelle de manière répétée jusqu'à ce qu'une condition de terminaison soit remplie. Elle est utilisée par exemple pour calculer les suites de Fibonacci, les factorielles, etc.
fib(5), fib(2) est recalculé 3 fois !Voici l'arbre d'appels récursifs de fib(5) sans mémorisation :
fib(5)
├── fib(4)
│ ├── fib(3)
│ │ ├── fib(2) ← recalculé
│ │ └── fib(1)
│ └── fib(2) ← recalculé
└── fib(3)
├── fib(2) ← recalculé
└── fib(1)La mémorisation comme solution
La mémorisation (memoization) est une technique d'enregistrement des résultats intermédiaires afin d'éviter des calculs répétés et d'accélérer les programmes. En Python, elle peut être réalisée élégamment à l'aide d'un décorateur.
Exemple — Suite de Fibonacci mémorisée
# Décorateur de mémorisation
def memo_fib(func):
memoire = {} # Cache : { num: résultat }
def calculer(num):
if num not in memoire:
memoire[num] = func(num) # Calcul et stockage
return memoire[num] # Retour depuis le cache
return calculer
@memo_fib
def fib(num):
if num < 2:
return 1
else:
return fib(num - 1) + fib(num - 2)
print(fib(5)) # 8
print(fib(10)) # 89
print(fib(30)) # 13462698 89 1346269
Voici comment fonctionne la mémorisation :
memo_fib: stocke les résultats intermédiaires dans le dictionnairememoire.fib: calcule la suite de Fibonacci. Grâce au concept de fermeture (closure), elle a accès à la variablememoirede la fonction englobante.- Lors de l'appel de
fib(5), chaque valeur est calculée une seule fois puis mise en cache. Les appels suivants retournent instantanément la valeur stockée.
Solution intégrée : functools.lru_cache
Python fournit un décorateur de mémorisation intégré et optimisé dans le module functools : @lru_cache (Least Recently Used cache).
Exemple — Avec lru_cache
from functools import lru_cache
@lru_cache(maxsize=None) # maxsize=None : cache illimité
def fib(n):
if n < 2:
return 1
return fib(n - 1) + fib(n - 2)
print(fib(50)) # 20365011074
print(fib.cache_info()) # CacheInfo(hits=48, misses=51, ...)@functools.lru_cache à un décorateur de mémorisation maison. Il est plus robuste, gère automatiquement la taille du cache, et fournit des statistiques via fonction.cache_info().Récapitulatif des décorateurs vus
| Décorateur | Rôle | Structure |
|---|---|---|
@decorateur simple | Enveloppe une fonction pour modifier son comportement | 2 niveaux : decorateur → wrapper |
@add_data | Attache un attribut à la fonction | 1 niveau, pas de wrapper |
@temps_execution | Mesure le temps d'exécution | 2 niveaux avec *args, **kwargs |
@decorateur(params) | Décorateur paramétré | 3 niveaux : externe → décorateur → wrapper |
@memo_fib | Mémorise les résultats (cache) | 2 niveaux + dictionnaire de cache |
@lru_cache | Mémorisation intégrée optimisée | Module functools (Python ≥ 3.2) |
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.