Menu
Coddy logo textTech

Décorateurs de classe

Fait partie de la section Programmation orientée objet du Journey Python de Coddy. Leçon 40 sur 64.

Les décorateurs de classes vous permettent de modifier ou d'améliorer des classes en les enveloppant avec une autre fonction. Ils fonctionnent comme les décorateurs de fonctions, mais s'appliquent à 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 cls

Appliquez le décorateur à une classe à l’aide de @ :

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

Maintenant, la classe possède à la fois les méthodes original et ajoutée :

person = EnhancedPerson("Alice")
print(person.greet())     # Bonjour, je m'appelle Alice
print(person.farewell())  # Au revoir de Alice

Vous pouvez également encapsuler une méthode existante : conserver l’originale afin de pouvoir toujours l’appeler à l’intérieur de l’encapsuleur. Cela 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’enveloppe s’exécute d’abord, puis délègue l’appel à l’original :

person = TrackedPerson("Alice")
print(person.greet())
# greet a été appelé
# Bonjour, je m'appelle Alice

Les étapes clés pour envelopper une method existante sont les suivantes :
1. Enregistrer la method original : original = cls.method
2. Définir une nouvelle function qui appelle original(self) ainsi que toute logique supplémentaire.
3. Réaffecter la nouvelle function à 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, puis renvoient la classe modifiée. Ils peuvent ajouter des méthodes, modifier des attributs ou encapsuler 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.

challenge icon

Défi

Moyen

Dans ce défi, vous allez implémenter un décorateur de classe qui ajoute une fonctionnalité de suivi des appels de méthodes.

  • decorator.py - Implémentez le décorateur de classe add_counter
  • calculator.py - Contient les classes qui utiliseront votre décorateur

Le fichier driver.py contient de nombreux scénarios de test qui valideront votre implémentation avec différents cas d’utilisation, schémas d’héritage et cas limites. Vous n’avez pas besoin de modifier ce fichier, mais son examen vous aidera à comprendre le comportement attendu.

Essayez vous-même

from calculator import Calculator

# Gestionnaire complet de cas de test
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)  # Should print {'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)  # Should print {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Devrait afficher 8
    print(calc.call_counts)  # Should print {'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)  # Should print {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Devrait afficher 8
    print(calc.call_counts)  # Should print {'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)  # Should print {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # Effectuer 1000 opérations add
    for i in range(1000):
        calc.add(i, i+1)
    
    # Effectuer 500 opérations subtract
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Should print {'add': 1000, 'subtract': 500}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur Python en ligne