Aller au contenu

Décorateurs#

avancé

Principe#

Un décorateur est une fonction qui prend une fonction en argument et renvoie une fonction (souvent une version enrichie).

def journaliser(fonction):
    def wrapper(*args, **kwargs):
        print(f"appel de {fonction.__name__}")
        resultat = fonction(*args, **kwargs)
        print(f"retour : {resultat}")
        return resultat
    return wrapper

@journaliser
def addition(a, b):
    return a + b

addition(2, 3)
# appel de addition
# retour : 5

Équivalent à : addition = journaliser(addition).

Préserver les métadonnées : functools.wraps#

from functools import wraps

def journaliser(fonction):
    @wraps(fonction)   # préserve __name__, __doc__ de la fonction décorée
    def wrapper(*args, **kwargs):
        return fonction(*args, **kwargs)
    return wrapper

Piège

Sans @wraps, addition.__name__ devient "wrapper" après décoration : perturbe l'introspection, les docs et les tests.

Décorateur avec paramètres#

def repeter(n):
    def decorateur(fonction):
        @wraps(fonction)
        def wrapper(*args, **kwargs):
            resultat = None
            for _ in range(n):
                resultat = fonction(*args, **kwargs)
            return resultat
        return wrapper
    return decorateur

@repeter(3)
def dire_bonjour():
    print("bonjour")

Décorateurs intégrés courants#

class Cercle:
    def __init__(self, rayon):
        self._rayon = rayon

    @property
    def rayon(self):
        return self._rayon

    @staticmethod
    def depuis_diametre(d):
        return Cercle(d / 2)

    @classmethod
    def unite(cls):
        return cls(1)

Décorateurs de functools#

from functools import lru_cache, cached_property

@lru_cache(maxsize=None)         # mémoïse les appels : arguments -> résultat
def fibonacci(n):
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

class Maillage:
    @cached_property             # calculé au 1er accès, puis conservé sur l'instance
    def volume(self):
        return sum(c.volume for c in self.cellules)

Piège

lru_cache exige des arguments hachables (pas de list ni de dict) et suppose la fonction pure : mémoïser une fonction qui lit un fichier ou dépend de l'heure renvoie des résultats périmés.

Empiler plusieurs décorateurs#

@journaliser
@repeter(2)
def f():
    ...
# équivalent à : f = journaliser(repeter(2)(f))
# ordre d'application : repeter d'abord, puis journaliser