Passer au contenu principal
eLearner.app
Module 9 · Leçon 4 sur 436/36 dans le cours~14 min
Leçons du module (4/4)

Décorateurs : fonctions modifiant des fonctions

Un décorateur (decorator) est une fonction qui prend une autre fonction et en renvoie une version "décorée". C'est un moyen puissant d'ajouter du comportement (journalisation, chronométrage, mise en cache, tentatives, validation) sans modifier le corps de la fonction d'origine.

Les fonctions comme valeurs

En Python, les fonctions sont des objets : vous pouvez les passer, les renvoyer, les assigner.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Un décorateur minimal

Python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"chiamo {func.__name__}")
        risultato = func(*args, **kwargs)
        print(f"ho finito {func.__name__}")
        return risultato
    return wrapper

def somma(a, b):
    return a + b

somma = log(somma)   # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5

Syntaxe de @decorator

L'affectation somma = log(somma) s'écrit de manière plus élégante avec @ :

Python
@log
def somma(a, b):
    return a + b

C'est exactement la même chose. @log au-dessus de la définition équivaut à : "dès que cette fonction est définie, remplace-la par log(fonction)".

*args, **kwargs : accepter n'importe quelle signature

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args collecte les arguments positionnels, **kwargs les arguments par mot-clé. Ils vous permettent d'écrire des wrappers qui fonctionnent avec n'importe quelle fonction.

functools.wraps : préserver les métadonnées

Sans wraps, la fonction décorée perd __name__, __doc__, les types-hints :

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Exemple : @timeit

Python
from functools import wraps
import time

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        risultato = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
        return risultato
    return wrapper

@timeit
def somma_grande():
    return sum(range(10**6))

somma_grande()

Décorateurs de la stdlib

Python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

fib(100)  # istantaneo grazie alla cache
  • @lru_cache — mémorise les résultats d'une fonction pure.
  • @property — expose une méthode sous forme d'attribut (vu dans le module OOP).
  • @staticmethod, @classmethod — modifient la façon dont self est transmis.

Décorateurs avec arguments

Les décorateurs peuvent également accepter des arguments. Pour implémenter cela, vous devez écrire trois niveaux de fonctions imbriquées : la fonction la plus externe reçoit les arguments du décorateur, la fonction intermédiaire reçoit la fonction décorée, et la fonction la plus interne (wrapper) reçoit les arguments réels passés à la fonction décorée lors des appels.

À vous de jouer

Exercice#python.m9.l4.e1
Tentatives : 0Chargement…

Écrivez un décorateur `double_result` qui appelle la fonction décorée et renvoie son résultat * 2. Appliquez-le à une fonction `f(x)` qui renvoie x + 1. Évaluez `f(10)`.

Chargement de l'éditeur…
Afficher l'indice

Le wrapper appelle func et renvoie le résultat multiplié par 2.

Solution disponible après 3 tentatives

Exercice de révision

Exercice#python.m9.l4.e2
Tentatives : 0Chargement…

Utilisez functools.lru_cache pour accélérer une fonction récursive fib(n). Calculez fib(30) et assignez-le à `r`. Évaluez `r`.

Chargement de l'éditeur…
Afficher l'indice

@lru_cache(maxsize=None) au-dessus du def.

Solution disponible après 3 tentatives

Défi supplémentaire

Exercice#python.m9.l4.e3
Tentatives : 0Chargement…

Définissez un décorateur `double_result` qui double (multiplie par 2) la valeur de retour de la fonction décorée. Appliquez le décorateur à la fonction `def sum_nums(a, b): return a + b`. Enfin, évaluez `sum_nums(3, 4)`.

Chargement de l'éditeur…
Afficher l'indice

Le wrapper appelle func(*args, **kwargs) et multiplie le résultat par 2.

Solution disponible après 3 tentatives