Patron Décorateur en Python
L’idée centrale du patron de décorateur est l’enveloppement — en créant un objet enveloppeur (décorateur) pour envelopper l’objet d’origine, ce qui permet d’étendre ses fonctionnalités sans modifier l’objet d’origine.
Concepts de base
Le patron de décorateur comprend quatre rôles principaux :
- Interface de composant (Component): définit l’interface commune pour l’objet décoré et le décorateur
- Composant concret: l’objet d’origine qui doit être décoré
- Classe de base du décorateur (Decorator): contient une référence à un objet composant et implémente l’interface du composant
- Décorateur concret: implémente la fonctionnalité de décoration spécifique

Syntaxe de base des décorateurs
Décorateurs de fonction
Les décorateurs de fonction sont la forme la plus courante de décorateurs. Ils prennent une fonction en argument et renvoient une nouvelle fonction.
Exemple
def wrapper(*args, **kwargs):
print("Quelques opérations avant l'exécution de la fonction")
result = func(*args, **kwargs)
print("Quelques opérations après l'exécution de la fonction")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Bonjour, {name}!")
# Utiliser le décorateur
say_hello("Alice")
Sortie :
函数执行前的一些操作 Hello, Alice! 函数执行后的一些操作
Décorateurs avec paramètres
Si vous devez passer des paramètres au décorateur, vous devez imbriquer une autre couche de fonctions :
Exemple
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(times):
print(f"Exécution {i+1}:")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Bonjour, {name}!")
greet("Bob")
Décorateurs de classe
Outre les décorateurs de fonctions, Python prend également en charge les décorateurs de classe. Les décorateurs de classe fonctionnent en implémentant__call__des méthodes pour fonctionner.
Décorateur de classe simple
Exemple
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
import time
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(f"Fonction {self.func.__name__} exécutée en {end_time - start_time:.4f} secondes")
return result
@TimerDecorator
def calculate_sum(n):
return sum(range(n))
result = calculate_sum(1000000)
print(f"Résultat du calcul : {result}")
Décorateur de classe avec paramètres
Exemple
def __init__(self, level="INFO"):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"[{self.level}] Appel de la fonction : {func.__name__}")
print(f"[{self.level}] Arguments : args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"[{self.level}] Valeur de retour : {result}")
return result
return wrapper
@LogDecorator(level="DEBUG")
def multiply(a, b):
return a * b
multiply(5, 3)
Décorateurs intégrés
Python fournit quelques décorateurs intégrés utiles :
@staticmethod et @classmethod
Exemple
@staticmethod
def add(x, y):
return x + y
@classmethod
def multiply(cls, x, y):
return x * y
# Utiliser une méthode statique
result1 = Calculator.add(5, 3)
print(f"Résultat de la méthode statique : {result1}")
# Utiliser une méthode de classe
result2 = Calculator.multiply(5, 3)
print(f"Résultat de la méthode de classe : {result2}")
@property
Exemple
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Le rayon doit être un nombre positif")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(f"Rayon : {circle.radius}")
print(f"Aire : {circle.area}")
circle.radius = 10
print(f"Nouveau rayon : {circle.radius}")
print(f"Nouvelle aire : {circle.area}")
Scénarios d'application pratiques des décorateurs
1. Journalisation
Exemple
def wrapper(*args, **kwargs):
print(f"Début de l'exécution : {func.__name__}")
try:
result = func(*args, **kwargs)
print(f"Terminé avec succès : {func.__name__}")
return result
except Exception as e:
print(f"Échec de l'exécution : {func.__name__}, erreur : {e}")
raise
return wrapper
@log_execution
def process_data(data):
# Simuler le traitement des données
if not data:
raise ValueError("Les données ne peuvent pas être vides")
return [x * 2 for x in data]
# Test
data = [1, 2, 3]
result = process_data(data)
print(f"Résultat du traitement : {result}")
2. Vérification des autorisations
Exemple
def wrapper(user, *args, **kwargs):
if not user.get('is_authenticated', False):
raise PermissionError("Utilisateur non connecté")
return func(user, *args, **kwargs)
return wrapper
@require_login
def view_profile(user):
return f"Afficher le profil de l'utilisateur {user['username']}"
# Test
user1 = {'username': 'alice', 'is_authenticated': True}
user2 = {'username': 'bob', 'is_authenticated': False}
print(view_profile(user1)) # Exécution normale
# print(view_profile(user2)) # lèvera PermissionError
3. Décorateur de cache
Exemple
cache = {}
def wrapper(*args):
if args in cache:
print(f"Obtenir le résultat depuis le cache : {args}")
return cache[args]
result = func(*args)
cache[args] = result
print(f"Calculer et mettre en cache le résultat : {args} -> {result}")
return result
return wrapper
@cache_results
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Tester l'effet du cache
print(fibonacci(5))
print(fibonacci(5)) # Cette fois, il sera récupéré depuis le cache
Ordre d'exécution de plusieurs décorateurs
Lorsque vous utilisez plusieurs décorateurs, leur ordre d'exécution va du bas vers le haut :
Exemple
def wrapper():
print("Décorateur 1 - avant")
func()
print("Décorateur 1 - après")
return wrapper
def decorator2(func):
def wrapper():
print("Décorateur 2 - avant")
func()
print("Décorateur 2 - après")
return wrapper
@decorator1
@decorator2
def my_function():
print("Fonction originale")
my_function()
Sortie :
装饰器1 - 前 装饰器2 - 前 原始函数 装饰器2 - 后 装饰器1 - 后
Préservation des métadonnées de fonction
Lorsque vous utilisez des décorateurs, les métadonnées de la fonction d'origine (comme le nom de la fonction, la docstring, etc.) sont écrasées par la fonction d'encapsulation. Vous pouvez utiliserfunctools.wrapspour préserver cette information :
Exemple
def preserve_metadata(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Docstring de la fonction d'encapsulation"""
return func(*args, **kwargs)
return wrapper
@preserve_metadata
def example_function():
"""Ceci est la docstring de la fonction d'origine"""
pass
print(f"Nom de la fonction : {example_function.__name__}")
print(f"Docstring : {example_function.__doc__}")
Exercices pratiques
Exercice 1 : Création d'un décorateur de surveillance des performances
Écrivez un décorateur pour surveiller le temps d'exécution et l'utilisation de la mémoire d'une fonction.
Exemple
import tracemalloc
def performance_monitor(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Démarrer le suivi de la mémoire
tracemalloc.start()
# Enregistrer l'heure de début
start_time = time.time()
# Exécuter la fonction
result = func(*args, **kwargs)
# Enregistrer l'heure de fin
end_time = time.time()
# Obtenir l'utilisation de la mémoire
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Rapport de performance pour la fonction {func.__name__}")
print(f"Temps d'exécution : {end_time - start_time:.4f} secondes")
print(f"Utilisation mémoire : actuelle {current/1024:.1f} KB, pic {peak/1024:.2f} KB")
return result
return wrapper
@performance_monitor
def process_large_data():
"""Simuler de grandes quantités de données"""
data = [i**2 for i in range(100000)]
return sum(data)
process_large_data()
Exercice 2 : Implémentation d'un décorateur de mécanisme de réessai
Créez un décorateur qui réessaie automatiquement un nombre spécifié de fois lorsqu'une exécution de fonction échoue.
Exemple
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
print(f"La fonction {func.__name__} a échoué, le nombre maximal de tentatives est atteint")
raise
print(f"La fonction {func.__name__} a échoué lors de la tentative {attempts} : {e}")
print(f"Attente de {delay} secondes avant de réessayer...")
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_operation():
"""Simule une opération instable"""
import random
if random.random() < 0.7: # probabilité d'échec de 70%
raise ValueError("Échec aléatoire")
return "Opération réussie"
# Test du mécanisme de réessai
result = unstable_operation()
print(f"Résultat final : {result}")
Résumé
Les décorateurs Python sont un outil puissant et flexible qui offre les avantages suivants :
- Réutilisation du code: Sépare les préoccupations transversales (comme la journalisation, la mise en cache, la validation) de la logique métier
- Extension dynamique: Ajouter de nouvelles fonctionnalités sans modifier le code d'origine
- Restez concis: Utilisez
@La syntaxe rend le code plus clair et plus lisible - Respecte le principe ouvert-fermé: Ouvert à l'extension, fermé à la modification
Bonnes pratiques d'utilisation des décorateurs :
- Utilisation
functools.wrapsPréserver les métadonnées des fonctions - Conserver la responsabilité unique du décorateur
- Gérer correctement les exceptions dans les décorateurs
- Prêter attention à l'ordre d'exécution des décorateurs