DĂ©corateurs Python đ : fonctions amĂ©liorĂ©es
Mise Ă jour :

Les dĂ©corateurs Python, câest un peu comme ajouter une couche de vernis Ă vos fonctions : ils permettent de modifier ou dâenrichir leur comportement, sans toucher Ă leur code. En gros, ils prennent une fonction, lâenrobent de logique supplĂ©mentaire et renvoient une version boostĂ©e. Que vous vouliez mesurer le temps dâexĂ©cution dâune mĂ©thode, vĂ©rifier des permissions ou simplement ajouter un peu de magie, les dĂ©corateurs sont vos alliĂ©s. Et le meilleur ? Une fois que vous les maĂźtrisez, vous ne pourrez plus vous en passer !
Comment fonctionnent les fonctions en Python
Pour bien comprendre les dĂ©corateurs, il faut dâabord se plonger dans un concept clĂ© de Python : les fonctions sont des objets de premiĂšre classe. Ăa veut dire quoi ? Tout simplement que les fonctions en Python sont comme nâimporte quel autre objet : elles peuvent ĂȘtre stockĂ©es dans des variables, passĂ©es en argument Ă dâautres fonctions, ou mĂȘme ĂȘtre retournĂ©es comme rĂ©sultats.
Prenons un exemple simple pour illustrer cela :
def dire_bonjour(): print("Bonjour !")
# Assigner la fonction Ă une variablesalutation = dire_bonjour
# Appeler la fonction via la nouvelle variablesalutation()Ici, salutation devient un alias de dire_bonjour. Les deux références
pointent vers le mĂȘme objet fonction.
On peut aussi passer une fonction comme argument Ă une autre. Câest ici que la magie des dĂ©corateurs commence Ă apparaĂźtre.
def executer_fonction(func): print("Je vais exécuter la fonction passée en argument...") func()
# Passer une fonction comme argumentexecuter_fonction(dire_bonjour)Résultat ? La fonction dire_bonjour est exécutée depuis executer_fonction.
Cela ouvre la voie à des opérations comme ajouter de la logique avant ou aprÚs
lâexĂ©cution dâune fonction.
Une fonction peut aussi en retourner une autre. Ce comportement est crucial pour comprendre comment les décorateurs modifient des fonctions.
def faire_salut(): def message(): print("Salut !") return message
# Appeler faire_salut et rĂ©cupĂ©rer une fonctionfonction_retournee = faire_salut()fonction_retournee()Ici, faire_salut retourne une nouvelle fonction, message, que lâon peut
appeler indépendamment.
Maintenant, combinons ces idĂ©es : une fonction peut Ă la fois recevoir une autre fonction et en retourner une. Câest littĂ©ralement ce que fait un dĂ©corateur.
def mon_decorateur(func): def wrapper(): print("Avant la fonction") func() print("AprĂšs la fonction") return wrapper
def dire_au_revoir(): print("Au revoir !")
# Décorer "à la main"decorated = mon_decorateur(dire_au_revoir)decorated()Ici, mon_decorateur enveloppe dire_au_revoir dans un nouveau comportement.
La fonction retournée, wrapper, contient les actions avant et aprÚs.
Comprendre que les fonctions sont des objets de premiĂšre classe est essentiel pour utiliser les dĂ©corateurs. Cela permet de saisir comment ils interceptent et enrichissent des fonctions existantes. Une fois cette idĂ©e en tĂȘte, les dĂ©corateurs deviennent un outil incroyablement puissant pour optimiser votre code !
Créer un décorateur simple
Maintenant que lâon sait que les fonctions en Python peuvent ĂȘtre passĂ©es et retournĂ©es comme des objets, crĂ©ons un dĂ©corateur basique. Un dĂ©corateur est, en gros, une fonction qui prend une autre fonction en entrĂ©e, lui ajoute du code avant ou aprĂšs et retourne une nouvelle fonction âenrichieâ.
Structure de base dâun dĂ©corateur
Voici la structure classique dâun dĂ©corateur :
def mon_decorateur(func): def wrapper(): # Code avant l'exĂ©cution de la fonction originale print("Avant la fonction") func() # Code aprĂšs l'exĂ©cution de la fonction originale print("AprĂšs la fonction") return wrapperDans cet exemple, mon_decorateur enveloppe une fonction avec un âemballageâ
(wrapper) contenant du code supplémentaire.
Utilisation dâun dĂ©corateur
Supposons que nous ayons une fonction dire_bonjour que nous voulons enrichir.
Voici comment lâappliquer manuellement :
def dire_bonjour(): print("Bonjour tout le monde !")
# Décorer la fonction "à la main"decorated = mon_decorateur(dire_bonjour)decorated()Cela produit :
Avant la fonctionBonjour tout le monde !AprĂšs la fonctionPlutĂŽt cool, non ? Mais il y a une façon encore plus simple dâappliquer un
décorateur : utiliser la syntaxe @.
La syntaxe @ pour simplifier
Python fournit une syntaxe dédiée pour appliquer un décorateur. Il suffit
dâutiliser le symbole @ avant le nom du dĂ©corateur :
@mon_decorateurdef dire_bonjour(): print("Bonjour tout le monde !")
# Appeler directement la fonctiondire_bonjour()Cette syntaxe est Ă©quivalente Ă lâexemple prĂ©cĂ©dent, mais bien plus lisible.
Décorer des fonctions avec des arguments
Et si notre fonction avait des arguments ? Pas de problĂšme, il suffit de passer
ces arguments Ă func dans le wrapper :
def mon_decorateur(func): def wrapper(*args, **kwargs): print("Avant la fonction") result = func(*args, **kwargs) print("AprĂšs la fonction") return result return wrapper
@mon_decorateurdef dire_bonjour_avec_nom(nom): print(f"Bonjour, {nom} !")
dire_bonjour_avec_nom("Alice")Sortie :
Avant la fonctionBonjour, Alice !AprĂšs la fonctionLe *args et **kwargs permettent de gĂ©rer nâimporte quel nombre dâarguments
positionnels ou nommĂ©s. Câest indispensable pour des fonctions gĂ©nĂ©riques.
Pourquoi utiliser des décorateurs ?
CrĂ©er un dĂ©corateur permet de rĂ©utiliser du code commun, comme ajouter des logs, mesurer le temps dâexĂ©cution, ou gĂ©rer des permissions. Par exemple, voici un dĂ©corateur pour mesurer la durĂ©e dâune fonction :
import time
def chronometre(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"Durée : {end - start:.4f} secondes") return result return wrapper
@chronometredef traitement_lourd(): time.sleep(2) # Simule un calcul lourd print("Traitement terminé !")
traitement_lourd()Sortie :
Traitement terminé !Durée : 2.0021 secondesEmpilement de décorateurs
Saviez-vous que vous pouvez appliquer plusieurs dĂ©corateurs Ă une mĂȘme fonction ? Oui, Python permet ce quâon appelle lâempilement de dĂ©corateurs, et câest Ă la fois pratique et puissant. LâidĂ©e est simple : chaque dĂ©corateur sâapplique successivement, dans lâordre oĂč ils sont dĂ©finis, pour enrichir votre fonction Ă©tape par Ă©tape.
Comment empiler des décorateurs
Pour empiler des décorateurs, il suffit de les écrire les uns au-dessus des
autres, avec le symbole @. Voici un exemple simple :
def decorateur_1(func): def wrapper(*args, **kwargs): print("Décorateur 1 : Avant la fonction") result = func(*args, **kwargs) print("Décorateur 1 : AprÚs la fonction") return result return wrapper
def decorateur_2(func): def wrapper(*args, **kwargs): print("Décorateur 2 : Avant la fonction") result = func(*args, **kwargs) print("Décorateur 2 : AprÚs la fonction") return result return wrapper
@decorateur_1@decorateur_2def ma_fonction(): print("Exécution de la fonction")
ma_fonction()Sortie :
DĂ©corateur 1 : Avant la fonctionDĂ©corateur 2 : Avant la fonctionExĂ©cution de la fonctionDĂ©corateur 2 : AprĂšs la fonctionDĂ©corateur 1 : AprĂšs la fonctionOrdre dâexĂ©cution
Lâordre des dĂ©corateurs est important. Dans lâexemple ci-dessus, decorateur_2
est appliqué avant decorateur_1 car Python traite les décorateurs du bas
vers le haut.
Autrement dit :
ma_fonctionest décorée pardecorateur_2.- Puis, le résultat est à nouveau décoré par
decorateur_1.
Exemple pratique : logs et chronométrage
Imaginons que vous souhaitiez Ă la fois ajouter des logs et mesurer le temps dâexĂ©cution dâune fonction. Voici comment faire :
import time
def log_execution(func): def wrapper(*args, **kwargs): print(f"Appel de la fonction {func.__name__} avec {args} et {kwargs}") return func(*args, **kwargs) return wrapper
def chronometre(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"Temps d'exécution de {func.__name__} : {end - start:.4f} secondes") return result return wrapper
@log_execution@chronometredef traitement_lourd(n): time.sleep(n) print("Traitement terminé !")
traitement_lourd(2)Sortie :
Appel de la fonction wrapper avec (2,) et {}Temps d'exĂ©cution de traitement_lourd : 2.0021 secondesTraitement terminĂ© !Ătendre lâempilement aux mĂ©thodes de classes
Les décorateurs empilés fonctionnent aussi trÚs bien sur des méthodes de classes. Par exemple, vous pouvez combiner des décorateurs pour vérifier les permissions et ajouter des logs :
def verifier_permissions(func): def wrapper(self, *args, **kwargs): if not self.est_admin: print("AccÚs refusé.") return return func(self, *args, **kwargs) return wrapper
def log_execution(func): def wrapper(*args, **kwargs): print(f"Exécution de {func.__name__}") return func(*args, **kwargs) return wrapper
class Systeme: def __init__(self, est_admin): self.est_admin = est_admin
@verifier_permissions @log_execution def supprimer_donnees(self): print("Données supprimées.")
utilisateur = Systeme(est_admin=False)utilisateur.supprimer_donnees()
admin = Systeme(est_admin=True)admin.supprimer_donnees()Sortie :
AccÚs refusé.Exécution de supprimer_donneesDonnées supprimées.Décorateurs dans des classes
Les dĂ©corateurs ne sont pas rĂ©servĂ©s aux fonctions globales. Vous pouvez aussi les appliquer aux mĂ©thodes et aux propriĂ©tĂ©s dâune classe pour ajouter des comportements ou simplifier la gestion de vos donnĂ©es. Câest une fonctionnalitĂ© puissante pour structurer un code objet propre et rĂ©utilisable.
En Python, il existe des décorateurs intégrés spécifiques pour gérer les méthodes dans une classe :
@staticmethod: indique quâune mĂ©thode nâa pas accĂšs Ă lâinstance ou Ă la classe elle-mĂȘme. Câest une fonction âindĂ©pendanteâ dans une classe.@classmethod: transforme une mĂ©thode pour quâelle reçoive la classe comme premier argument au lieu de lâinstance (clsau lieu deself).
Exemple avec @staticmethod
Une mĂ©thode statique est utile pour des fonctions qui ne dĂ©pendent pas des attributs de lâinstance ou de la classe.
class MathUtils: @staticmethod def addition(a, b): return a + b
# Appel direct sans créer d'instanceprint(MathUtils.addition(3, 5)) # Sortie : 8Exemple avec @classmethod
Une mĂ©thode de classe permet de travailler avec la classe elle-mĂȘme.
class Compteur: total = 0
@classmethod def incrementer(cls): cls.total += 1
# Utilisation de la méthode de classeCompteur.incrementer()Compteur.incrementer()print(Compteur.total) # Sortie : 2Décorateurs pour les propriétés
Un autre usage fréquent des décorateurs dans les classes est la gestion des
propriétés avec @property. Cela permet de définir des getters et
setters sans appeler explicitement des méthodes.
class Personne: def __init__(self, nom): self._nom = nom
@property def nom(self): return self._nom
@nom.setter def nom(self, nouveau_nom): if not nouveau_nom: raise ValueError("Le nom ne peut pas ĂȘtre vide.") self._nom = nouveau_nom
# Utilisationp = Personne("Alice")print(p.nom) # Sortie : Alicep.nom = "Bob"print(p.nom) # Sortie : BobIci, @property permet dâaccĂ©der Ă lâattribut _nom comme si câĂ©tait une
propriété publique tout en gardant le contrÎle sur sa validation.
Les décorateurs de Click
Dans un autre chapitre, nous avons vu comment les décorateurs enrichissent les fonctions. Click exploite cette puissance pour simplifier la création de CLI (Command Line Interface). En voici la liste :
@click.command: transforme une fonction en commande CLI.@click.option: ajoute des options avec prĂ©fixe (--nom).@click.argument: gĂšre les arguments positionnels.@click.group: regroupe plusieurs commandes sous une mĂȘme interface.
import click
@click.command()@click.option("--nom", default="utilisateur", help="Le nom Ă saluer.")def salut(nom): click.echo(f"Bonjour, {nom} !")
if __name__ == "__main__": salut()Conclusion
Les dĂ©corateurs Python sont une solution Ă©lĂ©gante pour enrichir vos fonctions et simplifier votre code. Quâil sâagisse de valider des donnĂ©es, de mesurer des performances ou de structurer des CLI avec des outils comme Click, ils offrent une modularitĂ© et une lisibilitĂ© incomparables.
En maĂźtrisant les dĂ©corateurs, vous pouvez Ă©crire un code plus propre et plus maintenable, tout en rĂ©duisant les rĂ©pĂ©titions inutiles. Les perspectives sont vastes : frameworks, outils dâautomatisation ou optimisation de vos propres projets. Lancez-vous et dĂ©couvrez Ă quel point les dĂ©corateurs peuvent transformer votre maniĂšre de coder !