Décorateurs de classe
Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 40 sur 64.
Les décorateurs de classe vous permettent de modifier ou d'améliorer des classes en les enveloppant avec une autre fonction. Ils fonctionnent comme les décorateurs de fonction, mais sont appliqués à des classes entières.
Voici une classe simple sans décoration :
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"Créez un décorateur de classe qui ajoute une nouvelle méthode :
def add_farewell(cls):
def farewell(self):
return f"Goodbye from {self.name}"
cls.farewell = farewell # Ajoute la méthode à la classe
return clsAppliquez le décorateur à une classe en utilisant @ :
@add_farewell
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"Désormais, la classe possède à la fois les méthodes originales et les méthodes ajoutées :
person = EnhancedPerson("Alice")
print(person.greet()) # Bonjour, mon nom est Alice
print(person.farewell()) # Au revoir de la part d'AliceVous pouvez également envelopper une méthode existante — en stockant l'originale afin de pouvoir toujours l'appeler à l'intérieur de l'enveloppe (wrapper). C'est utile lorsque vous souhaitez ajouter un comportement (comme le suivi ou la journalisation) autour d'une méthode que la classe possède déjà :
def add_tracking(cls):
original_greet = cls.greet # Stocker la méthode originale
def tracked_greet(self):
print(f"greet was called") # Comportement supplémentaire avant
return original_greet(self) # Appeler la méthode originale
cls.greet = tracked_greet # Remplacer la méthode sur la classe
return cls
@add_tracking
class TrackedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"Lorsque vous appelez greet maintenant, l'enveloppeur s'exécute en premier, puis délègue à l'original :
person = TrackedPerson("Alice")
print(person.greet())
# greet a été appelé
# Hello, my name is AliceLes étapes clés pour envelopper une méthode existante sont :
1. Sauvegarder la méthode originale : original = cls.method
2. Définir une nouvelle fonction qui appelle original(self) plus toute logique supplémentaire.
3. Réassigner la nouvelle fonction à la classe : cls.method = new_function
4. Retourner la classe modifiée.
Point clé : Les décorateurs de classe prennent une classe en entrée, la modifient ou l'améliorent, et renvoient la classe modifiée. Ils peuvent ajouter des méthodes, modifier des attributs ou envelopper des fonctionnalités existantes. Utilisez @decorator_name au-dessus de la définition de la classe pour les appliquer. Cela offre un moyen propre d'étendre les fonctionnalités d'une classe sans héritage.
Défi
MoyenDans ce défi, vous allez implémenter un décorateur de classe qui ajoute une fonctionnalité de suivi des appels de méthode.
decorator.py- Implémentez le décorateur de classeadd_countercalculator.py- Contient les classes qui utiliseront votre décorateur
Le fichier driver.py contient des scénarios de test approfondis qui valideront votre implémentation par rapport à divers cas d'utilisation, modèles d'héritage et cas limites. Vous n'avez pas besoin de modifier ce fichier, mais l'examiner vous aidera à comprendre le comportement attendu.
Aide-mémoire
Les décorateurs de classe modifient ou améliorent les classes en les enveloppant dans une autre fonction. Ils prennent une classe en entrée, la modifient et renvoient la classe modifiée.
Syntaxe de base d'un décorateur de classe :
def decorator_name(cls):
# Modifier la classe
return cls
@decorator_name
class MyClass:
passExemple - ajouter une méthode à une classe :
def add_farewell(cls):
def farewell(self):
return f"Goodbye from {self.name}"
cls.farewell = farewell # Ajouter la méthode à la classe
return cls
@add_farewell
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"
person = EnhancedPerson("Alice")
print(person.greet()) # Hello, my name is Alice
print(person.farewell()) # Goodbye from AliceExemple - envelopper une méthode existante (stocker l'originale, puis la remplacer) :
def loud_greet(cls):
original_greet = cls.greet # Stocker la méthode originale
def new_greet(self):
result = original_greet(self) # Appeler l'originale
return result.upper() # Améliorer le résultat
cls.greet = new_greet # Remplacer par la version enveloppée
return cls
@loud_greet
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"
person = EnhancedPerson("Alice")
print(person.greet()) # HELLO, MY NAME IS ALICEÉtapes clés pour envelopper une méthode existante :
original = cls.method_name — sauvegarder la méthode originaledef new_method(self): original(self) — l'appeler à l'intérieur de l'enveloppe (wrapper)cls.method_name = new_method — remplacer la méthode sur la classeLes décorateurs de classe peuvent ajouter des méthodes, modifier des attributs ou envelopper des fonctionnalités existantes sans utiliser l'héritage.
Essayez vous-même
from calculator import Calculator
# Gestionnaire de cas de test complet
test_case = input()
if test_case == "basic_functionality":
calc = Calculator()
print(calc.add(5, 3)) # Devrait afficher 8
print(calc.add(2, 7)) # Devrait afficher 9
print(calc.subtract(10, 4)) # Devrait afficher 6
print(calc.call_counts) # Devrait afficher {'add': 2, 'subtract': 1}
elif test_case == "multiple_instances":
calc1 = Calculator()
calc2 = Calculator()
print(calc1.add(5, 3)) # Devrait afficher 8
print(calc2.add(2, 7)) # Devrait afficher 9
print(calc1.subtract(10, 4)) # Devrait afficher 6
# Les deux instances devraient partager le même call_counts
print(calc1.call_counts) # Devrait afficher {'add': 2, 'subtract': 1}
print(calc2.call_counts) # Devrait afficher {'add': 2, 'subtract': 1}
print(calc1.call_counts is calc2.call_counts) # Devrait afficher True
elif test_case == "method_call_order":
calc = Calculator()
print(calc.add(1, 2)) # Devrait afficher 3
print(calc.subtract(5, 2)) # Devrait afficher 3
print(calc.add(10, 20)) # Devrait afficher 30
print(calc.add(7, 3)) # Devrait afficher 10
print(calc.call_counts) # Devrait afficher {'add': 3, 'subtract': 1}
elif test_case == "large_values":
calc = Calculator()
print(calc.add(1000000, 2000000)) # Devrait afficher 3000000
print(calc.subtract(5000000, 2000000)) # Devrait afficher 3000000
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 1}
elif test_case == "negative_values":
calc = Calculator()
print(calc.add(-5, -3)) # Devrait afficher -8
print(calc.subtract(-10, -4)) # Devrait afficher -6
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 1}
elif test_case == "float_values":
calc = Calculator()
print(calc.add(5.5, 3.2)) # Devrait afficher 8.7
print(calc.subtract(10.5, 4.2)) # Devrait afficher 6.3
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 1}
elif test_case == "zero_values":
calc = Calculator()
print(calc.add(0, 0)) # Devrait afficher 0
print(calc.subtract(0, 0)) # Devrait afficher 0
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 1}
elif test_case == "counter_reset":
calc = Calculator()
print(calc.add(5, 3)) # Devrait afficher 8
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 0}
# Réinitialiser le compteur
calc.call_counts = {"add": 0, "subtract": 0}
print(calc.add(2, 7)) # Devrait afficher 9
print(calc.call_counts) # Devrait afficher {'add': 0, 'subtract': 0}
elif test_case == "counter_manipulation":
calc = Calculator()
print(calc.add(5, 3)) # Devrait afficher 8
print(calc.call_counts) # Devrait afficher {'add': 1, 'subtract': 0}
# Manipuler le compteur directement
calc.call_counts["add"] = 100
print(calc.add(2, 7)) # Devrait afficher 9
print(calc.call_counts) # Devrait afficher {'add': 101, 'subtract': 0}
elif test_case == "performance_test":
calc = Calculator()
# Effectuer 1000 opérations d'addition
for i in range(1000):
calc.add(i, i+1)
# Effectuer 500 opérations de soustraction
for i in range(500):
calc.subtract(i+10, i)
print(calc.call_counts) # Devrait afficher {'add': 1000, 'subtract': 500}Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Les fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe paramètre selfMéthodesAttributsMéthode constructeur (__init__)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLa fonction super()Redéfinition de méthodeHéritage multipleOrdre de résolution des méthodesRécapitulatif - Hiérarchie des employés7Méthodes spéciales
Introduction aux méthodes magiquesSurcharge d'opérateursMéthodes magiques de conteneurRécapitulatif - Liste personnalisée10Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron Strategy2Décorateurs
Introduction aux décorateursDécorateur de propriétéDécorateur de méthode statiqueDécorateur de méthode de classe5Polymorphisme
Retour sur la redéfinition de méthodeDuck TypingClasses et méthodes abstraitesConception d'interfacesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageMixinsMéthodes statiques et de classeDécorateurs de classeGestionnaires de contexte11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Propriétés de classe
Variables d'instance vs variables de classeDécorateurs de propriétéAttributs privésRécapitulatif - Gestionnaire de compte bancaire