Introduction : La magie du @ en Python
Si tu as déjà croisé un étrange symbole @ au-dessus d'une fonction en Python et que tu t'es demandé à quoi il servait, tu es au bon endroit. Ce symbole, c'est celui d'un décorateur (ou decorator en anglais). C'est l'un des concepts les plus élégants et puissants de Python, mais aussi l'un de ceux qui peuvent sembler un peu mystérieux au premier abord. Pas de panique ! Dans cet article, on va démystifier tout ça ensemble, étape par étape. Tu vas voir qu'une fois le principe compris, les décorateurs deviennent un outil incroyable pour écrire un code plus propre, plus modulaire et plus professionnel. C'est exactement le genre de compétence qui fait la différence en NSI et dans tes futurs projets.
Les fonctions : des citoyens de première classe
Pour comprendre les décorateurs, il faut d'abord bien saisir un concept fondamental en Python : les fonctions sont des objets de première classe. Qu'est-ce que ça veut dire ? Tout simplement que tu peux :
- Assigner une fonction à une variable.
- Passer une fonction en argument à une autre fonction.
- Retourner une fonction depuis une autre fonction.
Regarde cet exemple simple :
def dire_bonjour(nom):
return f"Bonjour {nom}!"
# 1. Assignation à une variable
ma_fonction = dire_bonjour
print(ma_fonction("Alice")) # Affiche : Bonjour Alice!
# 2. Passage en argument
def appliquer_deux_fois(f, valeur):
return f(f(valeur))
# 3. Retour d'une fonction
def creer_multiplicateur(facteur):
def multiplier(n):
return n * facteur
return multiplierCette flexibilité est le socle sur lequel reposent les décorateurs. Une fonction qui en prend une autre en argument et qui en retourne une nouvelle modifiée, c'est exactement le principe de base d'un décorateur !
Construire son premier décorateur pas à pas
Imaginons que tu as une fonction qui fait un calcul et que tu veux chronométrer son temps d'exécution. Tu pourrais modifier le code de la fonction pour ajouter un chronomètre... mais c'est pénible et tu mélangerais la logique du calcul avec celle de la mesure. C'est là que le décorateur entre en jeu !
On va créer un décorateur chronometrer :
import time
# 1. Notre futur décorateur est une fonction qui prend une fonction en argument.
def mon_decorateur(fonction_a_decorer):
# 2. À l'intérieur, on définit une NOUVELLE fonction (la "wrapper").
def fonction_modifiee():
print("Début de l'exécution...")
resultat = fonction_a_decorer() # On appelle la fonction originale
print("Fin de l'exécution !")
return resultat
# 3. On retourne cette nouvelle fonction.
return fonction_modifiee
# Une fonction simple
def ma_tache():
print("Je fais mon travail.")
# 4. Application "manuelle" du décorateur
ma_tache_decoree = mon_decorateur(ma_tache)
ma_tache_decoree() # Affiche : Début... / Je fais... / Fin !Félicitations, tu viens de créer ton premier décorateur ! La syntaxe @ n'est qu'un sucre syntaxique (une façon plus jolie d'écrire) pour faire exactement cette opération. Au lieu des lignes 4 ci-dessus, on écrit simplement :
@mon_decorateur
def ma_tache():
print("Je fais mon travail.")
# Maintenant, quand on appelle ma_tache(), elle est automatiquement décorée.
ma_tache()Beaucoup plus propre, non ? Le décorateur a enveloppé ta fonction originale pour lui ajouter un comportement avant et après son exécution, sans que tu aies à toucher à son code.
Décorateurs utiles pour tes projets NSI
Maintenant que tu maîtrises le principe, voyons des applications concrètes qui vont booster tes projets.
1. Le décorateur de logging (journalisation)
Parfait pour déboguer ou suivre l'activité d'un programme.
def logger(fonction):
def wrapper(*args, **kwargs):
print(f"[LOG] Appel de {fonction.__name__} avec args={args}, kwargs={kwargs}")
resultat = fonction(*args, **kwargs)
print(f"[LOG] {fonction.__name__} a retourné : {resultat}")
return resultat
return wrapper
@logger
def addition(a, b):
return a + b
addition(5, 3) # Affiche les logs automatiquement.2. Le décorateur de validation des arguments
Assure-toi que les arguments passés à une fonction sont valides.
def valider_positif(fonction):
def wrapper(n):
if n <= 0:
raise ValueError("L'argument doit être positif !")
return fonction(n)
return wrapper
@valider_positif
def racine_carree(n):
return n ** 0.5
print(racine_carree(9)) # 3.0
# print(racine_carree(-4)) # Lève une ValueError !3. Le décorateur de mise en cache (mémoïsation)
Un classique pour optimiser les fonctions récursives coûteuses, comme la suite de Fibonacci.
def memoisation(fonction):
cache = {} # Dictionnaire pour stocker les résultats déjà calculés
def wrapper(n):
if n not in cache:
cache[n] = fonction(n)
return cache[n]
return wrapper
@memoisation
def fibo(n):
if n <= 1:
return n
return fibo(n-1) + fibo(n-2)
print(fibo(50)) # Calcul ultra-rapide grâce au cache !Ces exemples montrent la puissance des décorateurs : ils te permettent d'ajouter des fonctionnalités transversales (logging, validation, cache) de manière non intrusive et réutilisable.
Aller plus loin : décorateurs avec arguments et classes
Parfois, tu veux paramétrer ton décorateur. Par exemple, un décorateur @repeter(3) qui exécute la fonction 3 fois. Pour cela, il faut un décorateur... qui retourne un décorateur !
def repeter(nb_fois):
# Cette fonction est l'usine à décorateurs
def decorateur_usine(fonction):
# C'est le vrai décorateur
def wrapper():
for _ in range(nb_fois):
fonction()
return wrapper
return decorateur_usine
@repeter(nb_fois=3)
def dire_coucou():
print("Coucou !")
dire_coucou() # Affiche "Coucou !" trois fois.Tu peux aussi créer des décorateurs sous forme de classes, en utilisant la méthode spéciale __call__. C'est très utile si ton décorateur a besoin de garder un état complexe.
Conclusion : Un outil indispensable
Les décorateurs, c'est comme une superpuissance pour ton code Python. Ils te permettent de respecter un principe fondamental de la programmation : la séparation des préoccupations. La fonction fait son travail principal, et les décorateurs gèrent tout ce qui est autour (logs, sécurité, performances, etc.).
Au début, ça peut faire tourner la tête, mais avec un peu de pratique, l'utilisation du @ deviendra naturelle. N'hésite pas à revenir sur les exemples de cet article, à les tester dans ton interpréteur Python et à créer tes propres décorateurs pour tes projets NSI. C'est en forgeant qu'on devient forgeron !
