logo oujood
🔍

Les décorateurs Python (@decorator)

Un décorateur est une fonction qui en modifie une autre sans toucher à son code. Ce cours vous explique comment ça fonctionne et quand l'utiliser.

OUJOOD.COM

Un décorateur est une fonction qui prend une autre fonction en argument, lui ajoute un comportement, et retourne une version modifiée. Concrètement, au lieu de répéter le même code dans dix fonctions différentes — un contrôle d'accès, une mesure de temps, un log — vous l'écrivez une seule fois sous forme de décorateur et vous l'appliquez avec @nom_du_decorateur.

Derrière la syntaxe @, il n'y a rien de magique : c'est du Python ordinaire. Ce cours part de zéro pour vous montrer comment les décorateurs fonctionnent, comment en créer, et quand s'en servir.

Prérequis : les fonctions sont des objets en Python

Pour comprendre les décorateurs, il faut d'abord savoir qu'en Python, une fonction est un objet comme un autre. On peut la stocker dans une variable, la passer en argument, ou la retourner depuis une autre fonction.

  📋 Copier le code

def saluer():
    print("Bonjour !")

# On peut stocker une fonction dans une variable
action = saluer
action()
# Résultat : Bonjour !

# On peut passer une fonction en argument
def executer(fonction):
    fonction()

executer(saluer)
# Résultat : Bonjour !

Construire un décorateur à la main

Un décorateur est une fonction qui reçoit une fonction, définit une fonction interne (wrapper) qui ajoute du comportement, et retourne ce wrapper. Voici la structure de base :

  📋 Copier le code

def mon_decorateur(fonction):
    def wrapper():
        print("Avant l'appel")
        fonction()
        print("Après l'appel")
    return wrapper

def dire_bonjour():
    print("Bonjour !")

# Appliquer le décorateur manuellement
dire_bonjour = mon_decorateur(dire_bonjour)
dire_bonjour()
# Résultat :
# Avant l'appel
# Bonjour !
# Après l'appel

La syntaxe @ : le raccourci officiel

La ligne dire_bonjour = mon_decorateur(dire_bonjour) est exactement ce que fait Python quand vous écrivez @mon_decorateur juste au-dessus d'une fonction. C'est un raccourci syntaxique, rien de plus.

  📋 Copier le code

def mon_decorateur(fonction):
    def wrapper():
        print("Avant l'appel")
        fonction()
        print("Après l'appel")
    return wrapper

# Équivalent à : dire_bonjour = mon_decorateur(dire_bonjour)
@mon_decorateur
def dire_bonjour():
    print("Bonjour !")

dire_bonjour()
# Résultat :
# Avant l'appel
# Bonjour !
# Après l'appel

Décorer des fonctions avec des arguments

Le wrapper doit accepter et transmettre les arguments de la fonction décorée. On utilise *args et **kwargs pour que le décorateur fonctionne avec n'importe quelle signature.

  📋 Copier le code

def logger(fonction):
    def wrapper(*args, **kwargs):
        print(f"Appel de '{fonction.__name__}' avec {args} {kwargs}")
        resultat = fonction(*args, **kwargs)
        print(f"Retour : {resultat}")
        return resultat
    return wrapper

@logger
def additionner(a, b):
    return a + b

additionner(3, 5)
# Résultat :
# Appel de 'additionner' avec (3, 5) {}
# Retour : 8

Préserver la signature avec @wraps

Quand vous décorez une fonction, Python remplace son nom et sa documentation par ceux du wrapper. Pour conserver les métadonnées de la fonction originale, utilisez @wraps du module functools. C'est une bonne pratique à adopter systématiquement.

  📋 Copier le code

from functools import wraps

def logger(fonction):
    @wraps(fonction)  # Préserve __name__, __doc__, etc.
    def wrapper(*args, **kwargs):
        print(f"Appel de '{fonction.__name__}'")
        return fonction(*args, **kwargs)
    return wrapper

@logger
def multiplier(a, b):
    """Multiplie deux nombres."""
    return a * b

# Sans @wraps, __name__ retournerait 'wrapper'
print(multiplier.__name__)
# Résultat : multiplier
print(multiplier.__doc__)
# Résultat : Multiplie deux nombres.

Cas d'usage concrets

Mesurer le temps d'exécution

  📋 Copier le code

import time
from functools import wraps

def chronometre(fonction):
    @wraps(fonction)
    def wrapper(*args, **kwargs):
        debut = time.time()
        resultat = fonction(*args, **kwargs)
        duree = time.time() - debut
        print(f"{fonction.__name__} exécutée en {duree:.4f}s")
        return resultat
    return wrapper

@chronometre
def calcul_lent():
    time.sleep(0.5)
    return "terminé"

calcul_lent()
# Résultat : calcul_lent exécutée en 0.5002s

Vérifier qu'un utilisateur est connecté

  📋 Copier le code

from functools import wraps

def connexion_requise(fonction):
    @wraps(fonction)
    def wrapper(utilisateur, *args, **kwargs):
        if not utilisateur.get("connecte"):
            print("Accès refusé : vous devez être connecté.")
            return None
        return fonction(utilisateur, *args, **kwargs)
    return wrapper

@connexion_requise
def voir_profil(utilisateur):
    print(f"Profil de {utilisateur['nom']}")

user_connecte = {"nom": "Alice", "connecte": True}
user_deconnecte = {"nom": "Bob", "connecte": False}

voir_profil(user_connecte)
# Résultat : Profil de Alice
voir_profil(user_deconnecte)
# Résultat : Accès refusé : vous devez être connecté.

Décorateur avec ses propres arguments

Pour créer un décorateur qui accepte lui-même des paramètres, il faut ajouter un niveau d'imbrication : une fonction externe reçoit les arguments du décorateur, et retourne le vrai décorateur.

  📋 Copier le code

from functools import wraps

def repeter(n):
    # n est l'argument du décorateur
    def decorateur(fonction):
        @wraps(fonction)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                fonction(*args, **kwargs)
        return wrapper
    return decorateur

@repeter(3)
def dire_bonjour(nom):
    print(f"Bonjour, {nom} !")

dire_bonjour("Alice")
# Résultat :
# Bonjour, Alice !
# Bonjour, Alice !
# Bonjour, Alice !

Empiler plusieurs décorateurs

On peut appliquer plusieurs décorateurs sur une même fonction. Python les applique de bas en haut : le décorateur le plus proche de la fonction est appliqué en premier.

  📋 Copier le code

from functools import wraps

def majuscules(fonction):
    @wraps(fonction)
    def wrapper(*args, **kwargs):
        resultat = fonction(*args, **kwargs)
        return resultat.upper()
    return wrapper

def exclamation(fonction):
    @wraps(fonction)
    def wrapper(*args, **kwargs):
        resultat = fonction(*args, **kwargs)
        return resultat + " !!!"
    return wrapper

# Ordre d'application : exclamation(majuscules(saluer))
@exclamation
@majuscules
def saluer(nom):
    return f"bonjour {nom}"

print(saluer("Alice"))
# Résultat : BONJOUR ALICE !!!

Par carabde | Mis à jour le 21 avril 2026