Comment utiliser les décorateurs Python pour améliorer les fonctions

Les décorateurs Python sont un moyen puissant et flexible de modifier ou d'améliorer des fonctions et des méthodes. Les décorateurs permettent d'envelopper une fonction avec des fonctionnalités supplémentaires, ce qui vous permet d'étendre son comportement sans modifier son code réel. Cet article vous présentera le concept de décorateurs, comment les créer et les utiliser, et explorera quelques exemples pratiques.

Qu'est-ce qu'un décorateur ?

Un décorateur est une fonction qui prend une autre fonction et étend son comportement sans la modifier explicitement. En Python, les décorateurs sont souvent utilisés pour ajouter des fonctionnalités telles que la journalisation, le contrôle d'accès ou la mesure des performances à des fonctions ou méthodes existantes. Les décorateurs sont appliqués aux fonctions à l'aide de la syntaxe @decorator_name.

# Basic example of a decorator
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Comment fonctionnent les décorateurs

Lorsque vous appliquez un décorateur à une fonction, Python exécute essentiellement les étapes suivantes:

  1. La fonction décoratrice est appelée avec la fonction d'origine comme argument.
  2. La fonction décoratrice définit une nouvelle fonction (souvent appelée wrapper) qui améliore ou modifie le comportement de la fonction d'origine.
  3. La fonction décoratrice renvoie la nouvelle fonction.
  4. Lorsque la fonction décorée est appelée, elle appelle en fait la nouvelle fonction renvoyée par le décorateur.

Créer un décorateur simple

Créons un décorateur simple qui mesure le temps d'exécution d'une fonction. Cela est utile pour les tests de performances et l'optimisation.

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Execution time: {end_time - start_time} seconds")
        return result
    return wrapper

@timing_decorator
def slow_function():
    time.sleep(2)
    print("Function finished!")

slow_function()

Utilisation des décorateurs avec des arguments

Parfois, vous souhaiterez peut-être transmettre des arguments à votre décorateur. Pour ce faire, vous devez créer une fabrique de décorateurs, c'est-à-dire une fonction qui renvoie un décorateur. Voici un exemple de décorateur qui prend un argument pour spécifier un message personnalisé.

def custom_message_decorator(message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@custom_message_decorator("Starting the function...")
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Décorateurs pour les méthodes dans les classes

Les décorateurs peuvent également être utilisés avec des méthodes à l'intérieur des classes. Les utilisations courantes incluent la journalisation des appels de méthodes, le contrôle d'accès et la mise en cache des résultats. Voici un exemple d'utilisation d'un décorateur pour enregistrer les appels de méthodes dans une classe.

def log_method_call(method):
    def wrapper(self, *args, **kwargs):
        print(f"Calling {method.__name__} with arguments {args} and keyword arguments {kwargs}")
        return method(self, *args, **kwargs)
    return wrapper

class MyClass:
    @log_method_call
    def my_method(self, x, y):
        print(f"Result: {x + y}")

obj = MyClass()
obj.my_method(5, 7)

Enchaînement de décorateurs

Vous pouvez appliquer plusieurs décorateurs à une seule fonction. Ils sont appliqués du décorateur le plus interne au plus externe. Cela vous permet de composer différentes fonctionnalités ensemble. Voici un exemple d'enchaînement de deux décorateurs:

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def exclamation_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + "!"
    return wrapper

@exclamation_decorator
@uppercase_decorator
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))

Conclusion

Les décorateurs sont un outil polyvalent de Python qui vous permet d'améliorer ou de modifier le comportement des fonctions et des méthodes. En utilisant des décorateurs, vous pouvez ajouter des fonctionnalités réutilisables dans votre base de code sans modifier la logique de base de vos fonctions. Que ce soit pour la journalisation, la synchronisation ou la modification de la sortie, les décorateurs aident à garder votre code propre et maintenable. Entraînez-vous à utiliser les décorateurs pour devenir plus compétent et exploiter leur puissance dans vos projets Python.