Introduction
Dans ce laboratoire, vous découvrirez les Décorateurs Python (Python Decorators), une fonctionnalité puissante qui offre un moyen concis et réutilisable d'étendre la fonctionnalité d'une fonction ou d'une classe sans la modifier de manière permanente.
Vous commencerez par construire un décorateur simple, puis apprendrez à gérer les paramètres de fonction, à appliquer les meilleures pratiques à l'aide de functools.wraps, à créer des décorateurs qui acceptent leurs propres arguments, et enfin, à implémenter des décorateurs à l'aide de classes pour gérer l'état (state).
Décorateur squelette (Bare bone decorator)
Dans cette étape, vous apprendrez la structure fondamentale d'un décorateur Python. Un décorateur, dans sa forme la plus simple, est une fonction qui prend une autre fonction comme argument, lui ajoute une certaine fonctionnalité, puis retourne une autre fonction.
Note: Si vous n'êtes pas familier avec l'utilisation de WebIDE (similaire à VS Code), veuillez vous familiariser d'abord avec ce lab.
Tout d'abord, ouvrez le fichier step1.py dans l'explorateur de fichiers à gauche.
Maintenant, copiez le code suivant dans le fichier step1.py. Ce code définit un décorateur simple et l'applique à une fonction.
## Decorator: a function that takes another function and returns a wrapper
def your_decorator(func):
def wrapper():
## Do stuff before func...
print("Before func!")
func() ## Call the original function
## Do stuff after func...
print("After func!")
return wrapper ## Return the wrapper function
## @your_decorator is syntactic sugar for: foo = your_decorator(foo)
@your_decorator
def foo():
print("Hello World!")
foo() ## Calls wrapper, which calls foo with extra behavior
Décortiquons le code :
your_decorator(func): C'est la fonction décoratrice. Elle prend un argument,func, qui est la fonction à décorer.wrapper(): C'est une fonction interne qui "enveloppe" (wraps) la fonction originale. Elle contient le nouveau comportement que vous souhaitez ajouter. Ici, elle affiche des messages avant et après l'appel de la fonction originale.@your_decorator: C'est le sucre syntaxique (syntactic sugar) pour appliquer le décorateur. C'est équivalent à écrirefoo = your_decorator(foo)après la définition de la fonction.
Exécutez maintenant le script dans le terminal pour voir le décorateur en action.
python3 step1.py
Vous devriez voir la sortie suivante, qui montre que la fonction wrapper a été exécutée, ajoutant un comportement autour de la fonction originale foo.
Before func!
Hello World!
After func!
Décorateur pour une fonction avec paramètres
Dans cette étape, vous allez étendre votre décorateur pour qu'il fonctionne avec des fonctions qui acceptent des arguments. Une fonction réelle a souvent des paramètres, et notre décorateur doit être capable de les gérer correctement.
Pour ce faire, vous pouvez utiliser *args et **kwargs dans la signature de la fonction enveloppe (wrapper). Cela permet à l'enveloppe d'accepter n'importe quel nombre d'arguments positionnels et de mots-clés (keyword arguments) et de les transmettre à la fonction originale.
Ouvrez le fichier step2.py et ajoutez le code suivant :
## Decorator that works with functions that have parameters
def your_decorator(func):
def wrapper(*args,**kwargs): ## Accept any arguments
## Do stuff before func...
print("Before func!")
func(*args,**kwargs) ## Pass arguments to original function
## Do stuff after func...
print("After func!")
return wrapper
@your_decorator
def foo(bar):
print("My name is " + bar)
foo("Jack") ## Arguments are passed through wrapper
Dans ce code, la fonction wrapper est définie avec *args et **kwargs. Lorsque foo("Jack") est appelée, l'argument "Jack" est capturé par *args dans le wrapper puis transmis à la fonction originale func lorsque func(*args, **kwargs) est exécutée.
Exécutez le script pour voir le résultat.
python3 step2.py
La sortie montre que l'argument a été transmis avec succès à travers le décorateur à la fonction originale.
Before func!
My name is Jack
After func!
Modèle pour un décorateur de base
Dans cette étape, vous allez découvrir le modèle de meilleure pratique pour créer des décorateurs robustes. Bien que les décorateurs précédents fonctionnent, ils ont un effet secondaire : ils perdent les métadonnées de la fonction originale (comme son nom __name__ et sa chaîne de documentation __doc__).
Pour résoudre ce problème, le module functools de Python fournit un décorateur d'aide appelé wraps. De plus, un bon décorateur doit être capable de gérer les fonctions qui retournent des valeurs.
Ce modèle est utile pour la plupart des cas d'utilisation de décorateurs. Il est valide pour les fonctions avec ou sans paramètres, et avec ou sans valeur de retour.
Ouvrez le fichier step3.py et ajoutez le code suivant.
import functools
## Best practice decorator template: preserves function metadata and return value
def your_decorator(func):
@functools.wraps(func) ## Preserves function name, docstring, etc.
def wrapper(*args,**kwargs):
print("Before function execution...")
result = func(*args,**kwargs) ## Call function and capture return value
print("After function execution...")
return result ## Return the original function's return value
return wrapper
@your_decorator
def add(a, b):
"""This function adds two numbers."""
return a + b
sum_result = add(5, 3)
print(f"Function name: {add.__name__}")
print(f"Docstring: {add.__doc__}")
print(f"Result: {sum_result}")
Améliorations clés dans ce modèle :
import functools: Importe le module nécessaire.@functools.wraps(func): C'est un décorateur appliqué à la fonctionwrapper. Il copie les métadonnées de lafuncoriginale (comme__name__et__doc__) à la fonctionwrapper.result = func(*args, **kwargs): La valeur de retour de la fonction originale est capturée dans la variableresult.return result: La fonction enveloppe retourne le résultat capturé, de sorte que la fonction décorée se comporte comme prévu.
Exécutez le script :
python3 step3.py
La sortie démontre que le nom et la chaîne de documentation de la fonction sont préservés, et que la valeur de retour est gérée correctement.
Before function execution...
After function execution...
Function name: add
Docstring: This function adds two numbers.
Result: 8
Décorateur avec paramètres
Dans cette étape, vous apprendrez à créer des décorateurs qui peuvent être personnalisés avec leurs propres paramètres. Cela permet une utilisation des décorateurs plus flexible et configurable, par exemple @your_decorator(arg='value').
Pour y parvenir, vous devez créer une "usine à décorateurs" (decorator factory) — une fonction qui prend des arguments et retourne un décorateur. Cela ajoute une couche d'imbrication supplémentaire à notre structure.
Créons un décorateur qui prend une chaîne de caractères prefix et l'affiche avant et après l'appel de la fonction. Ouvrez step4.py et ajoutez le code suivant :
import functools
## Decorator factory: returns a decorator based on parameters
def prefix_decorator(prefix):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
## Use the 'prefix' argument from the factory
print(f"{prefix} Before function call")
result = func(*args, **kwargs)
print(f"{prefix} After function call")
return result
return wrapper
return decorator ## Return the actual decorator function
## Using the decorator with a parameter
@prefix_decorator(prefix="LOG:")
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
Voici comment cela fonctionne :
prefix_decorator(prefix="LOG:")est appelé en premier. C'est la fonction usine (factory).- Elle retourne la fonction
decorator, qui a maintenant accès à la variableprefix("LOG:") via une fermeture (closure). - Ce
decoratorretourné est ensuite appliqué àsay_hello, le décorant.
Exécutez le script pour voir comment le paramètre est utilisé.
python3 step4.py
La sortie montre le préfixe "LOG:", qui a été passé comme argument au décorateur.
LOG: Before function call
Hello, Alice!
LOG: After function call
Décorateurs basés sur des classes
Dans cette étape, vous allez explorer comment implémenter des décorateurs en utilisant des classes. Cette approche est particulièrement utile pour maintenir un état (state) entre les appels de fonction. Nous couvrirons deux modèles : décorer une méthode au sein d'une classe et définir un décorateur comme une classe elle-même.
Ouvrez le fichier step5.py pour commencer.
Modèle 1 : Décorer une méthode de classe
Vous pouvez définir un décorateur comme une méthode à l'intérieur d'une classe et l'appliquer à d'autres méthodes de cette même classe. Ceci est utile pour créer des comportements spécifiques liés au contexte de la classe.
Ajoutez le code suivant à step5.py. Cet exemple définit un décorateur pour gérer les exceptions pour une méthode de classe.
## Pattern 1: Decorating a method within a class
class DecorateMyMethod:
## Static method decorator for methods with only 'self' parameter
def decorator_for_class_method_with_no_args(method):
def wrapper_for_class_method(self): ## Only takes self
try:
return method(self) ## Call original method
except Exception as e:
print(f"\nWARNING: An exception occurred: {e}\n")
return wrapper_for_class_method
def __init__(self,succeed:bool):
self.succeed = succeed
@decorator_for_class_method_with_no_args
def class_action(self):
if self.succeed:
print("You succeeded by choice.")
else:
raise Exception("Epic fail of your own creation.")
print("--- Testing Pattern 1 ---")
test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
test_fail = DecorateMyMethod(False)
test_fail.class_action()
print("-" * 25)
Modèle 2 : Décorateur en tant que classe
Un modèle plus courant et plus puissant consiste à définir le décorateur lui-même comme une classe. Ceci est idéal lorsque le décorateur doit maintenir un état (state). En implémentant les méthodes __init__() et __call__(), vous pouvez faire en sorte qu'une instance de classe se comporte comme une fonction.
Créons un décorateur qui compte le nombre de fois où une fonction a été appelée. Ajoutez le code suivant à la fin de step5.py.
## Pattern 2: Class-based decorator for maintaining state
class CountCallNumber:
def __init__(self, func):
self.func = func ## Store the function to decorate
self.call_number = 0 ## Initialize call counter
def __call__(self, *args, **kwargs): ## Makes instance callable
self.call_number += 1 ## Increment counter
print("This is execution number " + str(self.call_number))
return self.func(*args, **kwargs) ## Call original function
@CountCallNumber
def say_hi(name):
print("Hi! My name is " + name)
print("\n--- Testing Pattern 2 ---")
say_hi("Jack")
say_hi("James")
__init__(self, func): C'est le constructeur. Il est appelé lorsque le décorateur est appliqué (@CountCallNumber). Il reçoit la fonction à décorer (say_hi) et la stocke.__call__(self, *args, **kwargs): Cette méthode rend l'instance de la classe appelable. Lorsque vous appelez la fonction décorée (say_hi("Jack")), vous invoquez en fait cette méthode__call__. Elle incrémente le compteur puis appelle la fonction originale stockée.
Maintenant, exécutez le script entier.
python3 step5.py
Vous verrez la sortie pour les deux modèles. Remarquez comment le compteur d'appels s'incrémente dans la deuxième partie.
--- Testing Pattern 1 ---
You succeeded by choice.
WARNING: An exception occurred: Epic fail of your own creation.
-------------------------
--- Testing Pattern 2 ---
This is execution number 1
Hi! My name is Jack
This is execution number 2
Hi! My name is James
Résumé
Félicitations pour avoir terminé ce laboratoire ! Vous avez acquis une solide compréhension des décorateurs Python et de leurs diverses formes.
Dans ce laboratoire, vous avez appris à :
- Créer un décorateur de base et comprendre la syntaxe
@. - Concevoir des décorateurs qui fonctionnent avec des fonctions acceptant un nombre quelconque d'arguments en utilisant
*argset**kwargs. - Utiliser le modèle de meilleure pratique avec
functools.wrapspour préserver les métadonnées de la fonction et gérer les valeurs de retour. - Construire des fabriques de décorateurs (decorator factories) qui permettent de personnaliser vos décorateurs avec des paramètres.
- Implémenter des décorateurs sous forme de classes pour maintenir un état (state) entre les appels de fonction, comme dans un compteur d'appels.
Les décorateurs sont une fonctionnalité clé pour écrire du code Python propre, réutilisable et extensible. Vous pouvez maintenant appliquer ces modèles pour améliorer vos propres projets.



