Menu
Coddy logo textTech

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 cls

Appliquez 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'Alice

Vous 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 Alice

Les é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.

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éthode.

  • 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 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:
    pass

Exemple - 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 Alice

Exemple - 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 originale
def 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 classe

Les 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}
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 Object Oriented Programming