Der Dekorator @property ermöglicht es, eine Methode in ein "virtuelles" Attribut einer Klasse zu verwandeln: Es erscheint für den Benutzer der Klasse als normales Attribut, wird jedoch von der Logik einer Python-Funktion gesteuert. Ursprünglich waren in Python alle Attribute einer Instanz direkt zugänglich, aber um die Kapselung zu unterstützen, benötigte man einen Mechanismus zur Steuerung des Zugriffs auf Daten, ohne das Interface der Klasse zu ändern.
Geschichte:
In frühen Versionen von Python gab es keinen expliziten Mechanismus zur Begrenzung des Zugriffs auf Attribute. Die Kapselungsaufgabe wurde durch Konventionen (z. B. Unterstriche) gelöst, aber jede Änderung der Logik zur Speicherung/Validierung von Werten brach die Kompatibilität. Mit dem Auftauchen des Dekorators @property wurde es möglich, eine Methode als Attribut zu deklarieren und sie mit Getter, Setter und Deleter auszustatten, während das frühere Interface der Klasse erhalten blieb.
Problem:
Wenn später Logik zur Validierung oder Berechnung des Wertes eines Feldes hinzugefügt werden muss, ist es teuer, das gesamte Interface neu zu konstruieren — man müsste alle Zugriffe auf die Variable ändern. Dabei sollte die Sichtbarkeit der internen Implementierung des Feldes für den externen Verbraucher der Klasse nicht offengelegt werden. @property ermöglicht es, den Zugriff leicht „zu virtualisieren“.
Lösung:
@property implementiert das Muster „Getter/Setter mit Schutz des Interfaces“: Es ist möglich, eine berechnete Eigenschaft hinzuzufügen oder die Festlegung von Werten zu kontrollieren, ohne den Client-Code zu ändern.
Beispielcode:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Die Temperatur liegt unter dem absoluten Nullpunkt!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # Verwendung obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError
Wesentliche Merkmale:
Kann man immer eine Property zu jedem Feld einer Klasse hinzufügen, ohne die Abwärtskompatibilität zu verletzen?
Nein. Wenn das Attribut öffentlich war und als normale Variable verwendet wurde, kann die Ersetzung durch eine Property transparent sein. Wenn jedoch irgendwo Zugriffe über __dict__ erfolgen oder Typprüfungen über type(obj.attr) durchgeführt werden, kann es zu unerwartetem Verhalten kommen.
Kann man nur einen Setter ohne Getter für eine Eigenschaft deklarieren?
Nein, zuerst ist ein Getter erforderlich (eine Methode mit @property), ansonsten weiß Python nicht, welchem Attribut der Setter „gebunden“ werden soll.
Wie ist die Reihenfolge der Dekoratoren @property/@setter/@deleter und warum?
Immer wird zuerst @property geschrieben (es erstellt das Property-Objekt), dann über den Namen der Methode — @<name>.setter und @<name>.deleter (sie ergänzen das zuvor erstellte Property).
class A: @property def value(self): ... @value.setter def value(self, v): ...
Die Felder der Klasse waren öffentlich, dann wurde eine berechnete Eigenschaft über eine Property nach der Veröffentlichung der öffentlichen API hinzugefügt. Im alten Code führt der Zugriff auf das Attribut implizit zu einem geänderten Verhalten oder löst einen Fehler aus.
Vorteile:
Nachteile:
__dict__ hatteBei der ursprünglichen Planung wurden die Felder als privat (mit einem Unterstrich) definiert und die Benutzer arbeiteten nur über Methoden/Properties. Das Hinzufügen neuer Logik in die Property verlief in Zukunft ohne Änderungen am Client-Interface.
Vorteile:
Nachteile: