Décorateurs
Des fonctions qui enveloppent d’autres fonctions pour leur ajouter un comportement, par ex. journalisation ou contrôle d’accès. Notation @nom.
Exemple
#import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Appel de {func.__name__}{args}")
result = func(*args, **kwargs)
print(f"{func.__name__} a renvoyé {result!r}")
return result
return wrapper
@log_call
def award(name, exp):
"""Attribue des points d'expérience."""
return f"{name}: +{exp} EXP"
award("Léa", 15)
print(award.__name__, "|", award.__doc__)Définition et utilisation
#Un décorateur est une fonction qui reçoit une autre fonction et renvoie une version enrichie de celle-ci. Écrire @log_call au-dessus de la définition d’une fonction est un raccourci pour award = log_call(award). Le mécanisme fonctionne parce qu’en Python les fonctions sont des objets ordinaires : on peut les passer en argument et les renvoyer depuis d’autres fonctions.
Un décorateur typique définit à l’intérieur une fonction d’enrobage (wrapper). Elle accepte n’importe quels arguments grâce à *args et **kwargs, fait quelque chose avant d’appeler l’original, l’appelle, fait quelque chose après et renvoie son résultat. Vous pouvez ainsi ajouter de la journalisation, une mesure du temps, un contrôle d’accès ou des nouvelles tentatives sans toucher aux fonctions elles-mêmes.
Le wrapper masque le nom et la docstring de la fonction d’origine : sans étape supplémentaire, award.__name__ renverrait "wrapper". Le décorateur @functools.wraps(func) appliqué au wrapper copie ces informations, il vaut donc toujours la peine de l’utiliser. Un décorateur avec ses propres paramètres, par ex. @require_level(5), demande un niveau de plus : la fonction externe reçoit les paramètres et renvoie le véritable décorateur.
Plusieurs décorateurs au-dessus d’une même fonction s’appliquent de bas en haut : celui qui est le plus proche de la définition l’enveloppe en premier. La bibliothèque standard fournit de nombreux décorateurs prêts à l’emploi, par ex. @property, @classmethod, @dataclass ou @functools.lru_cache.
Syntaxe
#import functools
def decorateur(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
resultat = func(*args, **kwargs)
return resultat
return wrapper
@decorateur
def fonction():
...Plus d’exemples
#import functools
def require_level(minimum):
def decorator(func):
@functools.wraps(func)
def wrapper(player, *args, **kwargs):
if player["level"] < minimum:
return f"{player['name']}: niveau {minimum} requis"
return func(player, *args, **kwargs)
return wrapper
return decorator
@require_level(5)
def enter_world(player, world):
return f"{player['name']} entre dans le monde {world}"
print(enter_world({"name": "Léa", "level": 7}, 3))
print(enter_world({"name": "Lucas", "level": 2}, 3))def bold(func):
def wrapper():
return f"**{func()}**"
return wrapper
def exclaim(func):
def wrapper():
return f"{func()} !"
return wrapper
@bold
@exclaim
def title():
return "Nouveau niveau"
print(title())Cette écriture équivaut à title = bold(exclaim(title)) : le point d’exclamation est donc ajouté avant la mise en gras.
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def check_answer(answer):
return answer == "B"
for answer in ["A", "B", "C"]:
check_answer(answer)
print(check_answer.calls)Bonnes pratiques
#- Ajoutez toujours
@functools.wraps(func)à la fonction d’enrobage. Vous conservez le nom et la docstring de l’original, ce qui facilite le débogage et la lecture des messages d’erreur. - Le wrapper doit accepter
*args, **kwargset renvoyer le résultat de l’original. Sansreturn, chaque fonction décorée se mettra à renvoyerNone. - Le code du décorateur lui-même s’exécute une seule fois, à la définition de la fonction, alors que le wrapper s’exécute à chaque appel. Ce qui doit se produire à chaque appel va à l’intérieur du wrapper.
Entrées associées
#- *args et **kwargsRassembler un nombre quelconque d’arguments positionnels dans un tuple et d’arguments nommés dans un dictionnaire, et dépaqueter des collections lors d’un appel.
- @propertyTransforme une méthode en attribut : la valeur est calculée à la lecture, et un setter optionnel contrôle l’écriture.
Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.