Les décorateurs en Python
Introduction
Un décorateur est une fonction qui prend une autre fonction en argument et retourne une nouvelle fonction modifiée (ou la même fonction avec des fonctionnalités supplémentaires). C'est un moyen pratique d'ajouter des fonctionnalités à des fonctions existantes sans modifier leur code source.
Les décorateurs sont souvent utilisés pour :
- La journalisation (logging)
- Le contrôle des accès (authentification/autorisation)
- La gestion du temps d'exécution
- La mise en cache
Exemple de base
def mon_decorateur(fonction):
def fonction_modifiee():
print("Avant l'exécution de la fonction.")
fonction()
print("Après l'exécution de la fonction.")
return fonction_modifiee
@mon_decorateur
def dire_bonjour():
print("Bonjour !")
dire_bonjour()
Explications :
- `@mon_decorateur` est équivalent à `dire_bonjour = mon_decorateur(dire_bonjour)`.
- Lors de l'appel à `dire_bonjour()`, Python exécute `fonction_modifiee()` du décorateur.
- Résultat :
Avant l'exécution de la fonction.
Bonjour !
Après l'exécution de la fonction.
Décorateur avec des arguments
def decorateur_arguments(fonction):
def fonction_modifiee(*args, **kwargs):
print("Arguments passés :", args, kwargs)
return fonction(*args, **kwargs)
return fonction_modifiee
@decorateur_arguments
def addition(a, b):
return a + b
resultat = addition(5, 3)
print("Résultat :", resultat)
Résultat :
Arguments passés : (5, 3) {}
Résultat : 8
Mesurer le temps d'exécution
import time
def mesurer_temps(fonction):
def wrapper(*args, **kwargs):
debut = time.time()
resultat = fonction(*args, **kwargs)
fin = time.time()
print(f"Temps d'exécution : {fin - debut:.4f} secondes")
return resultat
return wrapper
@mesurer_temps
def operation_lourde():
time.sleep(2)
print("Traitement terminé.")
operation_lourde()
Résultat :
Traitement terminé.
Temps d'exécution : 2.0001 secondes
Décorateur avec paramètres (authentification)
def verifier_utilisateur(role_requis):
def decorateur(fonction):
def wrapper(utilisateur, *args, **kwargs):
if utilisateur.get("role") == role_requis:
return fonction(utilisateur, *args, **kwargs)
else:
print("Accès refusé : permissions insuffisantes.")
return wrapper
return decorateur
@verifier_utilisateur("admin")
def afficher_donnees_sensibles(utilisateur):
print("Données sensibles affichées !")
admin = {"nom": "Alice", "role": "admin"}
invite = {"nom": "Bob", "role": "invité"}
afficher_donnees_sensibles(admin)
afficher_donnees_sensibles(invite)
Résultat :
Données sensibles affichées !
Accès refusé : permissions insuffisantes.
Décorateurs dans les frameworks web : Flask et FastAPI
Les frameworks web en Python comme Flask et FastAPI utilisent intensivement les décorateurs pour associer une URL à une fonction, et définir comment l’application doit répondre aux requêtes HTTP.
Ces décorateurs offrent un moyen simple et élégant de structurer une API ou une application web.
Dans Flask
Le décorateur @app.route()
permet d’associer une fonction Python à une route HTTP. Lorsque l’utilisateur visite une URL spécifique, Flask exécute automatiquement la fonction associée.
Par exemple :
@app.route("/")
def accueil():
return "Bienvenue sur ma page Flask !"
Ce décorateur signifie : « Lorsque quelqu’un accède à l’URL /
, exécute la fonction accueil()
».
Sans décorateur, on devrait écrire la même logique ainsi :
def accueil():
return "Bienvenue !"
app.add_url_rule("/", view_func=accueil)
Dans FastAPI
FastAPI suit une logique similaire à Flask mais va encore plus loin grâce à l’utilisation des annotations de type et à la génération automatique de documentation.
Le décorateur @app.get("/")
déclare un endpoint HTTP GET à la racine de l’API. La fonction décorée est automatiquement exposée comme route web, et sa réponse est convertie en JSON :
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def lire_racine():
return {"message": "Bienvenue sur l'API FastAPI !"}
Ce décorateur signifie : « Lorsque quelqu’un accède à l’URL /
avec une méthode GET, exécute lire_racine()
et retourne sa réponse en JSON ».
FastAPI va plus loin en utilisant les types Python pour :
- valider automatiquement les paramètres de requête,
- documenter les routes dans une interface Swagger (
/docs
),
- générer des messages d’erreur clairs si les types ne sont pas respectés.
Exemple avec paramètres :
@app.get("/addition")
def addition(a: int, b: int):
return {"résultat": a + b}
Appelé avec /addition?a=3&b=5
, FastAPI répondra :
{"résultat": 8}
C’est cette simplicité combinée à la rigueur des types qui fait la force de FastAPI pour créer des APIs modernes et bien documentées.