Menu
CoddyTech

Property-Decorators – Fortgeschritten

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

Erweiterte Eigenschaftsdekoratoren ermöglichen eine ausgefeiltere Kontrolle über den Attributzugriff, einschließlich berechneter Eigenschaften, Löschmethoden und einer vollständigen Verwaltung von Eigenschaften.

Hier ist ein Beispiel für berechnete Eigenschaften, die Werte aus anderen Attributen ableiten:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property
    def area(self):
        return self.width * self.height
    
    @property
    def perimeter(self):
        return 2 * (self.width + self.height)

Verwende berechnete Eigenschaften wie gewöhnliche Attribute:

rect = Rectangle(5, 3)
print(rect.area)      # 15 - automatisch berechnet
print(rect.perimeter) # 16 - automatisch berechnet

Erstelle eine Eigenschaft mit Getter, Setter und Deleter:

class Temperature:
    def __init__(self):
        self._temp = 0
    
    @property
    def temperature(self):
        return self._temp
    
    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._temp = value
    
    @temperature.deleter
    def temperature(self):
        print("Resetting temperature to 0")
        self._temp = 0

Verwende den vollständigen Funktionsumfang der Property:

temp = Temperature()

Verwende den Setter mit Validierung:

temp.temperature = 25
print(temp.temperature)  # 25

# temp.temperature = -300  # Würde ValueError auslösen

Verwende den Deleter:

del temp.temperature
print(temp.temperature)  # 0

Erstelle ein komplexeres Beispiel mit einem Spielstand:

class Player:
    def __init__(self, name):
        self.name = name
        self._score = 0
        self._level = 1
    
    @property
    def score(self):
        return self._score
    
    @score.setter
    def score(self, value):
        if value >= 0:
            self._score = value
            self._level = (value // 1000) + 1
        else:
            raise ValueError("Score cannot be negative")
    
    @score.deleter
    def score(self):
        print(f"Resetting {self.name}'s progress")
        self._score = 0
        self._level = 1
    
    @property
    def level(self):
        return self._level

player = Player("Alice")
player.score = 2500
print(f"Score: {player.score}, Level: {player.level}")  # Punktzahl: 2500, Level: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Punktzahl: 0, Level: 1

Ausgabe:

15
16
25
Resetting temperature to 0
0
Score: 2500, Level: 3
Resetting Alice's progress
Score: 0, Level: 1

Wichtiger Punkt: Erweiterte Property-Dekoratoren ermöglichen berechnete Properties (aus anderen Daten berechnet), das Löschen von Properties mit @property.deleter und die vollständige Kontrolle über das Abrufen, Setzen und Löschen von Attributen. So entstehen intuitive Schnittstellen, während eine strenge Datenvalidierung und Kapselung erhalten bleiben.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementierst du eine Rectangle-Klasse mit geeigneter Kapselung und Eigenschaftsvalidierung.

  • rectangle.py – Diese Datei musst du bearbeiten. Sie enthält TODO-Kommentare, die dich bei der Implementierung anleiten.
  • driver.py – Enthält umfangreiche Testszenarien (nicht ändern)
  1. Implementiere private Attribute für _width und _height
  2. Erstelle Eigenschaften für width und height mit Validierung (müssen positiv sein)
    • Löse die entsprechenden ValueError-Meldungen aus, wie in den TODOs angegeben
    • Wichtig: Prüfe, ob der Wert kleiner oder gleich 0 ist, und löse dann den Fehler aus (dies gewährleistet ein einheitliches Verhalten).
  3. Implementiere schreibgeschützte Eigenschaften für area und perimeter
  4. Erstelle eine dimensions-Eigenschaft mit Getter-, Setter- und Deleter-Funktionalität, wie in den TODOs beschrieben
    • Wichtig: Verwende im Setter die Syntax zum Entpacken eines Tupels: width, height = dimensions

Probier es selbst

from rectangle import Rectangle

# Verarbeitung des Testfalls
test_case = input()

# Test der grundlegenden Funktionalität
if test_case == "default_test":
    rect = Rectangle(5, 3)
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
    
    # Die Eigenschaft dimensions testen
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # Validierung testen
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # Deleter testen
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# Test mit Nullwerten
elif test_case == "zero_values":
    try:
        rect = Rectangle(0, 5)
    except ValueError as e:
        print(f"Error creating rectangle: {e}")
    
    try:
        rect = Rectangle(5, 0)
    except ValueError as e:
        print(f"Error creating rectangle: {e}")

# Test mit negativen Werten
elif test_case == "negative_values":
    rect = Rectangle(5, 3)
    original_dimensions = rect.dimensions
    
    try:
        rect.dimensions = (5, -3)
    except ValueError as e:
        print(f"Error setting dimensions: {e}")
    
    print(f"Dimensions after failed update: {rect.dimensions}")
    print(f"Original dimensions preserved: {rect.dimensions == original_dimensions}")

# Test mit großen Werten
elif test_case == "large_values":
    rect = Rectangle(1000000, 2000000)
    print(f"Large rectangle area: {rect.area}")
    print(f"Large rectangle perimeter: {rect.perimeter}")

# Test mit Gleitkommawerten
elif test_case == "float_values":
    rect = Rectangle(3.5, 2.75)
    print(f"Dimensions: {rect.dimensions}")
    print(f"Area: {rect.area}")
    print(f"Perimeter: {rect.perimeter}")
    
    rect.dimensions = (1.1, 2.2)
    print(f"New area with float dimensions: {rect.area}")

# Test mit Typfehlern
elif test_case == "type_errors":
    try:
        rect = Rectangle("5", 3)
    except Exception as e:
        print(f"Type error during creation: {type(e).__name__}: {e}")
    
    rect = Rectangle(5, 3)
    try:
        rect.dimensions = 10  # Kein Tupel
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# Mehrere Eigenschaftsoperationen testen
elif test_case == "property_operations":
    rect = Rectangle(5, 10)
    print(f"Initial - Width: {rect.width}, Height: {rect.height}, Area: {rect.area}")
    
    rect.width = 8
    print(f"After width change - Width: {rect.width}, Area: {rect.area}")
    
    rect.height = 6
    print(f"After height change - Height: {rect.height}, Area: {rect.area}")
    
    rect.dimensions = (12, 9)
    print(f"After dimensions change - Dimensions: {rect.dimensions}, Area: {rect.area}")
    
    del rect.dimensions
    print(f"After reset - Dimensions: {rect.dimensions}, Area: {rect.area}")

# Grenzfälle der Validierung von Eigenschaften testen
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # Ein sehr kleiner positiver Wert sollte akzeptiert werden
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# Mehrere Rechtecke testen
elif test_case == "multiple_rectangles":
    rect1 = Rectangle(5, 3)
    rect2 = Rectangle(10, 2)
    rect3 = Rectangle(4, 4)
    
    print(f"Rectangle 1 - Area: {rect1.area}, Perimeter: {rect1.perimeter}")
    print(f"Rectangle 2 - Area: {rect2.area}, Perimeter: {rect2.perimeter}")
    print(f"Rectangle 3 - Area: {rect3.area}, Perimeter: {rect3.perimeter}")
    
    # Jedes Rechteck ändern
    rect1.width = 7
    rect2.height = 5
    rect3.dimensions = (6, 6)
    
    print(f"After modifications:")
    print(f"Rectangle 1 - Dimensions: {rect1.dimensions}")
    print(f"Rectangle 2 - Dimensions: {rect2.dimensions}")
    print(f"Rectangle 3 - Dimensions: {rect3.dimensions}")

# Leistung bei vielen Operationen testen
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Viele Eigenschaftsoperationen ausführen
    for i in range(1000):
        rect.width = i % 10 + 1  # Werte 1-10
        rect.height = i % 5 + 1  # Werte 1-5
        area = rect.area  # Auf berechnete Eigenschaft zugreifen
        perimeter = rect.perimeter  # Auf berechnete Eigenschaft zugreifen
        dims = rect.dimensions  # Auf den Getter der Eigenschaft zugreifen
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
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