Python-Kurs / Objektwerte mit Methoden und Properties kontrollieren

Methoden beschreiben das Verhalten eines Objekts; eine Property verbindet die vertraute Attributschreibweise mit Regeln für das Lesen oder Zuweisen eines Wertes. Wenn die Initialisierung und spätere Änderungen dieselbe Prüfung verwenden, kann eine ungültige gewöhnliche Zuweisung zurückgewiesen werden, ohne den bisherigen Wert zu ersetzen.

Öffentliche Schnittstelle und internen Speicher trennen

Im Beispiel heißt die öffentliche Schnittstelle amount, während der Wert intern unter _amount gespeichert wird. @property macht die Lesemethode zum Getter: item.amount liest über diese Methode, ohne Klammern. @amount.setter ergänzt die Schreibregel für item.amount = value.

Der führende Unterstrich kennzeichnet interne Implementierung nach Konvention. Er schützt keine Geheimnisse und verhindert nicht, dass fremder Code ausdrücklich auf _amount zugreift. Eine Property ist eine kontrollierte normale Schnittstelle, keine Sicherheitsgrenze. Die Referenz zu property beschreibt Getter und Setter.

Der Initialisierer schreibt bewusst self.amount = amount. Damit erreicht schon die anfängliche Zuweisung denselben Setter wie eine spätere Änderung. Gegenüber der einfachen Klasse mit Prüfung nur im Initialisierer wird die Regel hier für gewöhnliche spätere Zuweisungen erneut angewendet.

Eine gültige Änderung und eine Ablehnung ausführen

Speichern Sie den vollständigen Code als properties.py in UTF-8 im Übungsordner und starten Sie python properties.py mit dem Projektinterpreter. Unter macOS oder Ubuntu kann außerhalb einer aktivierten Umgebung python3 passen. Zusatzpakete und externe Dateien sind nicht nötig.

class Expense:
    def __init__(self, amount):
        self.amount = amount

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, value):
        if type(value) is not int or value < 0:
            raise ValueError("amount must be a nonnegative integer")
        self._amount = value

    def add_fee(self, fee):
        self.amount = self.amount + fee

item = Expense(8000)
item.add_fee(500)
print(item.amount)
try:
    item.amount = -1
except ValueError:
    print("rejected")
print(item.amount)

Erwartete Ausgabe:

8500
rejected
8500

Die Initialisierung setzt 8000 über den Setter. add_fee(500) liest den aktuellen Betrag, berechnet 8500 und weist ihn ebenfalls über den Setter zu. Die anschließende direkte Zuweisung -1 wird abgewiesen. Weil der Setter erst prüft und nur danach speichert, bleibt der bisherige gültige Betrag 8500 erhalten.

Die Regel verlangt ausdrücklich einen gewöhnlichen eingebauten int mit Wert mindestens 0. True ist zwar eine Instanz der int-Unterklasse bool, sein genauer Typ ist aber nicht int und er wird als direkt zugewiesener Betrag zurückgewiesen. Auch 3.5 oder eine numerische Zeichenkette erfüllen diesen Vertrag nicht. Hier wird keine stillschweigende Umwandlung versucht.

Rekursion vermeiden und vor dem Speichern prüfen

Zugriff Weg im Beispiel
item.amount Getter liefert _amount
item.amount = 100 Setter prüft und speichert danach in _amount
item.add_fee(500) Methode berechnet den neuen Betrag und verwendet erneut den Setter

Schreiben Sie im Setter nicht self.amount = value. Diese Anweisung würde denselben Setter erneut aufrufen, der wiederum sich selbst aufruft, bis Python die Rekursion mit einem Fehler beendet. Die Speicherung erfolgt deshalb unter dem anderen internen Namen self._amount.

Die Reihenfolge ist ebenfalls Teil der Regel: erst sämtliche erforderlichen Prüfungen, dann die Zustandsänderung. Würde ein Setter den neuen Wert zuerst speichern und danach eine Ausnahme auslösen, könnte das Objekt trotz Fehlermeldung bereits verändert sein. Prüfen Sie nach jeder abgelehnten Aktualisierung den tatsächlichen alten Wert, nicht allein den aufgetretenen Ausnahmetyp.

Eine Property sollte eine nachvollziehbare Wertabfrage oder -änderung darstellen. Verstecken Sie umfangreiche Dateioperationen oder Netzwerkanfragen nicht unnötig hinter einer scheinbar einfachen Abfrage. Eine benannte Methode zeigt eine gesonderte oder möglicherweise aufwendige Operation deutlicher. Der Descriptor-Leitfaden zu Properties erläutert den zugrunde liegenden Zugriff.

Betragsregel und Methodenvertrag getrennt testen

  1. Ersetzen Sie in einem getrennten Test die abgelehnte Zuweisung durch item.amount = True und danach durch item.amount = 3.5. Beide müssen ValueError auslösen; prüfen Sie anschließend jeweils den unveränderten Betrag 8500.
  2. Erzeugen Sie ein neues Objekt mit 0. Dies ist ein gültiger Anfangsbetrag. Erzeugen Sie getrennt eines mit -1: Schon die Initialisierung muss über dieselbe Prüfung scheitern.
  3. Rufen Sie auf einem Objekt mit 8000 testweise add_fee(-500) auf. Der resultierende Betrag 7500 ist nach der vorhandenen Regel gültig. add_fee(-9000) würde dagegen einen negativen Endbetrag ergeben und darf den alten Wert nicht ersetzen.
  4. Stellen Sie sämtliche Eingaben wieder auf die ursprünglichen Werte und vergleichen Sie die drei Ausgabezeilen.

Die Methode prüft die Gebühr nicht unabhängig vom Ergebnis. Beispielsweise kann add_fee(True) rechnerisch 1 addieren und einen gültigen int-Endbetrag ergeben, obwohl die direkte Zuweisung eines booleschen Betrags abgewiesen wird. Ob eine negative oder boolesche Gebühr zulässig sein soll, ist eine zusätzliche Methodenregel. Wenn Gebühren ausschließlich nichtnegative gewöhnliche ganze Zahlen sein sollen, prüfen Sie diese Bedingung am Eingang von add_fee, bevor Sie rechnen.

Eine Property ohne Setter weist gewöhnliche Zuweisungen über diese Property mit AttributeError zurück. Das macht jedoch weder alle internen Daten unzugänglich noch das gesamte Objekt tief unveränderlich. Halten Sie die erlaubten Zugriffswege und die Bedeutung der Werte ausdrücklich fest und testen Sie Initialisierung, gültige Änderungen und abgelehnte Änderungen getrennt.