Menu
Coddy logo textTech

Klassendekoratoren

Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 40 von 64.

Klassen-Dekoratoren ermöglichen es Ihnen, Klassen zu modifizieren oder zu erweitern, indem sie mit einer anderen Funktion umschlossen werden. Sie funktionieren wie Funktions-Dekoratoren, werden aber auf ganze Klassen angewendet.

Hier ist eine einfache Klasse ohne Dekoration:

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

Erstellen Sie einen Klassen-Dekorator, der eine neue Methode hinzufügt:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Füge die Methode der Klasse hinzu
    return cls

Wenden Sie den Decorator auf eine Klasse an, indem Sie @ verwenden:

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

Jetzt verfügt die Klasse sowohl über die ursprünglichen als auch über die hinzugefügten Methoden:

person = EnhancedPerson("Alice")
print(person.greet())     # Hallo, mein Name ist Alice
print(person.farewell())  # Auf Wiedersehen von Alice

Sie können auch eine bestehende Methode kapseln – indem Sie das Original speichern, damit Sie es innerhalb des Wrappers weiterhin aufrufen können. Dies ist nützlich, wenn Sie Verhalten (wie Tracking oder Logging) um eine Methode herum hinzufügen möchten, die die Klasse bereits besitzt:

def add_tracking(cls):
    original_greet = cls.greet  # Die ursprüngliche Methode speichern

    def tracked_greet(self):
        print(f"greet was called")   # Zusätzliches Verhalten davor
        return original_greet(self)  # Die ursprüngliche Methode aufrufen

    cls.greet = tracked_greet  # Die Methode in der Klasse ersetzen
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

Wenn Sie greet jetzt aufrufen, wird zuerst der Wrapper ausgeführt und delegiert dann an das Original:

person = TrackedPerson("Alice")
print(person.greet())
# greet wurde aufgerufen
# Hallo, mein Name ist Alice

Die wichtigsten Schritte zum Umschließen einer bestehenden Methode sind:
1. Speichern Sie die ursprüngliche Methode: original = cls.method
2. Definieren Sie eine neue Funktion, die original(self) sowie zusätzliche Logik aufruft.
3. Weisen Sie die neue Funktion wieder der Klasse zu: cls.method = new_function
4. Geben Sie die modifizierte Klasse zurück.

Wichtiger Punkt: Klassen-Dekoratoren nehmen eine Klasse als Eingabe entgegen, modifizieren oder erweitern sie und geben die modifizierte Klasse zurück. Sie können Methoden hinzufügen, Attribute ändern oder bestehende Funktionalität umschließen. Verwenden Sie @decorator_name über der Klassendefinition, um sie anzuwenden. Dies bietet eine saubere Möglichkeit, die Klassenfunktionalität ohne Vererbung zu erweitern.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementieren Sie einen Klassen-Dekorator, der eine Funktionalität zur Verfolgung von Methodenaufrufen hinzufügt.

  • decorator.py - Implementieren Sie den add_counter Klassen-Dekorator
  • calculator.py - Enthält die Klassen, die Ihren Dekorator verwenden werden

Die Datei driver.py enthält umfangreiche Testszenarien, die Ihre Implementierung gegen verschiedene Anwendungsfälle, Vererbungsmuster und Grenzfälle validieren. Sie müssen diese Datei nicht ändern, aber ihre Untersuchung wird Ihnen helfen, das erwartete Verhalten zu verstehen.

Spickzettel

Klassen-Dekoratoren modifizieren oder erweitern Klassen, indem sie diese mit einer anderen Funktion umhüllen. Sie nehmen eine Klasse als Eingabe, modifizieren sie und geben die modifizierte Klasse zurück.

Grundlegende Syntax für Klassen-Dekoratoren:

def decorator_name(cls):
    # Modifiziere die Klasse
    return cls

@decorator_name
class MyClass:
    pass

Beispiel – Hinzufügen einer Methode zu einer Klasse:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Füge die Methode der Klasse hinzu
    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

Beispiel – Umhüllen einer bestehenden Methode (Original speichern, dann ersetzen):

def loud_greet(cls):
    original_greet = cls.greet  # Speichere die ursprüngliche Methode

    def new_greet(self):
        result = original_greet(self)  # Rufe das Original auf
        return result.upper()          # Erweitere das Ergebnis

    cls.greet = new_greet  # Ersetze durch die umhüllte Version
    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

Wichtige Schritte zum Umhüllen einer bestehenden Methode:

original = cls.method_name — Speichern der ursprünglichen Methode
def new_method(self): original(self) — Aufruf innerhalb des Wrappers
cls.method_name = new_method — Ersetzen der Methode in der Klasse

Klassen-Dekoratoren können Methoden hinzufügen, Attribute modifizieren oder bestehende Funktionalitäten umhüllen, ohne Vererbung zu nutzen.

Probier es selbst

from calculator import Calculator

# Umfassender Testfall-Handler
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # Sollte 8 ausgeben
    print(calc.add(2, 7))  # Sollte 9 ausgeben
    print(calc.subtract(10, 4))  # Sollte 6 ausgeben
    print(calc.call_counts)  # Sollte {'add': 2, 'subtract': 1} ausgeben

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # Sollte 8 ausgeben
    print(calc2.add(2, 7))  # Sollte 9 ausgeben
    print(calc1.subtract(10, 4))  # Sollte 6 ausgeben
    
    # Beide Instanzen sollten die gleichen call_counts teilen
    print(calc1.call_counts)  # Sollte {'add': 2, 'subtract': 1} ausgeben
    print(calc2.call_counts)  # Sollte {'add': 2, 'subtract': 1} ausgeben
    print(calc1.call_counts is calc2.call_counts)  # Sollte True ausgeben

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # Sollte 3 ausgeben
    print(calc.subtract(5, 2))  # Sollte 3 ausgeben
    print(calc.add(10, 20))  # Sollte 30 ausgeben
    print(calc.add(7, 3))  # Sollte 10 ausgeben
    print(calc.call_counts)  # Sollte {'add': 3, 'subtract': 1} ausgeben

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # Sollte 3000000 ausgeben
    print(calc.subtract(5000000, 2000000))  # Sollte 3000000 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 1} ausgeben

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # Sollte -8 ausgeben
    print(calc.subtract(-10, -4))  # Sollte -6 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 1} ausgeben

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # Sollte 8.7 ausgeben
    print(calc.subtract(10.5, 4.2))  # Sollte 6.3 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 1} ausgeben

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # Sollte 0 ausgeben
    print(calc.subtract(0, 0))  # Sollte 0 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 1} ausgeben

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Sollte 8 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 0} ausgeben
    
    # Den Zähler zurücksetzen
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Sollte 9 ausgeben
    print(calc.call_counts)  # Sollte {'add': 0, 'subtract': 0} ausgeben

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Sollte 8 ausgeben
    print(calc.call_counts)  # Sollte {'add': 1, 'subtract': 0} ausgeben
    
    # Den Zähler direkt manipulieren
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # Sollte 9 ausgeben
    print(calc.call_counts)  # Sollte {'add': 101, 'subtract': 0} ausgeben

elif test_case == "performance_test":
    calc = Calculator()
    
    # 1000 Additionsoperationen durchführen
    for i in range(1000):
        calc.add(i, i+1)
    
    # 500 Subtraktionsoperationen durchführen
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Sollte {'add': 1000, 'subtract': 500} ausgeben
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Object Oriented Programming