2.23. Décorateurs

Un décorateur est un élément de syntaxe qui enveloppe une fonction (ou une méthode) dans une autre fonction. L’enveloppe peut ajouter un comportement avant et après l’appel d’origine, remplacer la valeur de retour ou attacher des métadonnées. La forme est :

@wrapper
def f():
    ...

La ligne @wrapper est équivalente à f = wrapper(f) – la fonction f est construite normalement, puis transmise à wrapper, et le résultat est réassocié au nom f.

Une fonction entrant dans une boîte « wrapper » à gauche et en ressortant à droite sous la forme d'une fonction décorée.

Un décorateur prend une fonction en entrée et renvoie une nouvelle fonction.

2.23.1. Décorateurs de méthodes intégrés

Quelques décorateurs sont fournis avec Python et s’utilisent à l’intérieur des corps de classe.

2.23.1.1. @property

Transforme une méthode en attribut calculé. L’appelant y accède comme s’il s’agissait d’un simple attribut (sans parenthèses), mais une méthode s’exécute à chaque lecture :

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Utilisez-le lorsqu’un attribut qui semble simple nécessite un petit calcul en coulisses. Si le travail est coûteux, préférez une méthode ordinaire – les appelants ne s’attendent pas à ce que la lecture d’un attribut soit lente.

2.23.1.2. @classmethod

Définit une méthode qui reçoit la classe comme premier argument au lieu d’une instance. Le premier paramètre est conventionnellement nommé cls :

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Les méthodes de classe sont la manière standard de fournir des constructeurs alternatifs – des fonctions fabriques qui renvoient une instance construite d’une façon non standard.

2.23.1.3. @staticmethod

Définit une méthode qui ne reçoit ni l’instance ni la classe – ce n’est qu’une simple fonction qui réside dans l’espace de noms de la classe pour des raisons d’organisation :

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Utilisez-le avec parcimonie ; si une fonction n’a vraiment rien à voir avec l’état de la classe, une simple fonction au niveau du module est généralement plus propre.

2.23.2. Écrire un décorateur personnalisé

Un décorateur est une fonction qui prend une fonction et renvoie une fonction. La forme minimale :

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Sortie

calling add

Le wrapper capture func par fermeture et lui transmet tout. *args / **kwargs lui permet de fonctionner sur n’importe quelle fonction, pas seulement celles à deux arguments. Ce modèle est le fondement de décorateurs plus élaborés (chronométrage, mise en cache, nouvelle tentative en cas d’échec) mais le cœur reste toujours le même : prendre une fonction en entrée, renvoyer une fonction en sortie.