Xavier Lamorlette

Python : décorateurs

Sommaire :

Définition d'un décorateur

Un décorateur permet d'envelopper une fonction en la modifiant. Un décorateur prend une fonction en argument et retourne une fonction.

from functools import wraps

def print_arguments(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print(f"Arguments: {args} {kwargs}")
        return function(*args, **kwargs)
    return wrapper

@print_arguments
def a_function(a, b): ...

Ajouter @print_arguments au-dessus de def a_funtion() équivaut à :

def a_function(a, b): ...

a_funtion = print_arguments(a_function)

L'utilisation, optionnelle, de @wraps permet de conserver le nom et la docstring de la fonction lors de l'introspection du code.

Définition d'un décorateur via une classe

On peut définir un décorateur via une classe qui implémente la méthode __call__() :

from typing import Callable

class CallsCounter:
    _function: Callable
    _nb_calls: int = 0

    def __init__(self,
                 function: Callable):
        self._function = function

    def __call__(self, *args, **kwargs):
        self._nb_calls += 1
        return self._function(*args, **kwargs)

@CallsCounter
def a_function(a, b): ...

Décorer une méthode en utilisant un membre

Via une classe de base

from typing import Callable
from functools import wraps

class ActorWithErrorCallback:
    _on_error: Callable[[Exception, str], None]

    def __init__(self,
                 on_error: Callable[[Exception, str], None]):
        self._on_error = on_error

    def _try_except(message: str):
        def decorator(function):
            @wraps(function)
            def wrapper(self, *args, **kwargs):
                try:
                    function(self, *args, **kwargs)
                except Exception as error:
                    self._on_error(error, message)
            return wrapper
        return decorator

class Actor(ActorWithErrorCallback):
    @ActorWithErrorCallback._try_except("Error while doing toto")
    def toto(self, a, b):
        print(f"Actor.toto(): a={a}, b={b}")
        raise RuntimeError("the error")

def on_error(error: Exception,
             message: str):
    print(f"{message}: {error}")

actor = Actor(on_error)
actor.toto(1, 2)

En redéfinissant la méthode

from typing import Callable
from functools import wraps

def try_except(on_error: Callable[[Exception, str], None],
               message: str):
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            try:
                function(*args, **kwargs)
            except Exception as error:
                on_error(error, message)
        return wrapper
    return decorator

class Actor:
    _on_error: Callable[[Exception, str], None]

    def __init__(self,
                 on_error: Callable[[Exception, str], None]):
        self._on_error = on_error
        self.toto = try_except(self._on_error, "Error while doing toto")(self.toto)

    def toto(self, a, b):
        print(f"Actor.toto(): a={a}, b={b}")
        raise RuntimeError("the error")

def on_error_callback(error: Exception,
                      message: str):
    print(f"{message}: {error}")

actor = Actor(on_error_callback)
actor.toto(1, 2)

Attribut de fonction

On peut définir un attribut de fonction pour un wrapper. Exemple :

count_calls(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        wrapper.nb_calls += 1
        print(f"{function.__name__} called {wrapper.nb_calls} times")
        return function(*args, **kwargs)
    wrapper.nb_calls = 0
    return wrapper

La dernière mise à jour de cette page date de mai 2024.

Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.