Menu
Coddy logo textTech

Property-Decorators – Fortgeschritten

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

Fortgeschrittene Eigenschaftsdekoratoren ermöglichen eine ausgefeiltere Kontrolle über den Attributzugriff, einschließlich berechneter Eigenschaften, Löschern und der vollständigen Eigenschaftenverwaltung.

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 reguläre 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 die vollständige property-Funktionalität:

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}")  # Punktestand: 2500, Level: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Punktestand: 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 Eigenschaftsdekoratoren ermöglichen berechnete Eigenschaften (berechnet aus anderen Daten), das Löschen von Eigenschaften mit @property.deleter sowie die vollständige Kontrolle über das Abrufen, Setzen und Löschen von Attributen. Dadurch entstehen intuitive Schnittstellen, während eine zuverlässige Datenvalidierung und Kapselung erhalten bleiben.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementierst du eine Rectangle-Klasse mit ordnungsgemäßer Kapselung und Eigenschaftsvalidierung.

  • rectangle.py – Dies ist die Datei, die du bearbeiten musst. Sie enthält TODO-Kommentare, die dich bei der Implementierung unterstützen.
  • 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 geeignete 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 konsistentes 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 von Tupeln: width, height = dimensions

Probier es selbst

from rectangle import Rectangle

# Testfall-Handler
test_case = input()

# Test der Grundfunktionalitä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}")
    
    # Test the dimensions property
    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}")

# Test mehrerer Property-Operationen
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}")

# Test von Grenzfällen der Property-Validierung
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # 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}")

# Teste mehrere Rechtecke
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}")
    
    # Ändere jedes Rechteck
    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}")

# Teste die Leistung mit vielen Operationen
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Führe viele Property-Operationen durch
    for i in range(1000):
        rect.width = i % 10 + 1  # Werte 1-10
        rect.height = i % 5 + 1  # Werte 1-5
        area = rect.area  # Zugriff auf berechnete Property
        perimeter = rect.perimeter  # Zugriff auf berechnete Property
        dims = rect.dimensions  # Zugriff auf Property-Getter
    
    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