Menu
Coddy logo textTech

Klassen-Dekoratoren

Teil des Abschnitts Objektorientierte Programmierung der Python-Journey von Coddy. Lektion 40 von 64.

Klassen-Dekoratoren ermöglichen es dir, Klassen zu verändern oder zu erweitern, indem du sie mit einer anderen Funktion umschließt. Sie funktionieren wie Funktionsdekoratoren, 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}"

Erstelle einen Klassendekorator, 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 zur Klasse hinzu
    return cls

Wende den Decorator mithilfe von @ auf eine Klasse an:

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

Jetzt hat die Klasse sowohl die ursprünglichen als auch die hinzugefügten Methoden:

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

Du kannst auch eine vorhandene Methode umschließen: Speichere das Original, damit du es weiterhin innerhalb des Wrappers aufrufen kannst. Das ist nützlich, wenn du Verhalten (wie Tracking oder Protokollierung) zu einer Methode hinzufügen möchtest, die die Klasse bereits besitzt:

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

    def tracked_greet(self):
        print(f"greet was called")   # Zusätzliches Verhalten vorher
        return original_greet(self)  # Rufe die ursprüngliche Methode auf

    cls.greet = tracked_greet  # Ersetze die Methode auf der Klasse
    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 du jetzt greet aufrufst, wird zuerst der Wrapper ausgeführt und anschließend an das Original delegiert:

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

Die wichtigsten Schritte zum Umschließen einer vorhandenen Methode sind:
1. Die ursprüngliche Methode speichern: original = cls.method
2. Eine neue Funktion definieren, die original(self) sowie zusätzliche Logik aufruft.
3. Die neue Funktion wieder der Klasse zuweisen: cls.method = new_function
4. Die geänderte Klasse zurückgeben.

Wichtiger Punkt: Klassendekoratoren nehmen eine Klasse als Eingabe, verändern oder erweitern sie und geben die veränderte Klasse zurück. Sie können Methoden hinzufügen, Attribute verändern oder vorhandene Funktionalität umschließen. Verwende @decorator_name über der Klassendefinition, um sie anzuwenden. Dies bietet eine übersichtliche Möglichkeit, die Funktionalität einer Klasse ohne Vererbung zu erweitern.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementierst du einen Klassendekorator, der eine Funktionalität zur Nachverfolgung von Methodenaufrufen hinzufügt.

  • decorator.py – Implementiere den Klassendekorator add_counter
  • calculator.py – Enthält die Klassen, die deinen Dekorator verwenden werden

Die Datei driver.py enthält umfangreiche Testszenarien, die deine Implementierung anhand verschiedener Anwendungsfälle, Vererbungsmuster und Sonderfälle überprüfen. Du musst diese Datei nicht ändern, aber ihre Untersuchung hilft dir, das erwartete Verhalten zu verstehen.

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)  # Should print {'add': 2, 'subtract': 1}

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 dieselben call_counts teilen
    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)  # 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)  # Should print {'add': 3, 'subtract': 1}

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)  # Should print {'add': 1, 'subtract': 1}

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)  # Should print {'add': 1, 'subtract': 1}

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)  # Should print {'add': 1, 'subtract': 1}

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)  # Should print {'add': 1, 'subtract': 1}

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

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

elif test_case == "performance_test":
    calc = Calculator()
    
    # 1000 add-Operationen durchführen
    for i in range(1000):
        calc.add(i, i+1)
    
    # 500 subtract-Operationen durchführen
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Should print {'add': 1000, 'subtract': 500}
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 Objektorientierte Programmierung

Übe selbstständig: Online-Python-Compiler