Die Python-Funktion property() macht aus Methoden berechnete Attribute — von außen sieht es aus wie ein normales Attribut, intern läuft eine Funktion. Mit Getter, Setter und Deleter lassen sich Validierung, Lazy-Loading und Read-Only-Felder ohne separate get_*/set_*-Methoden umsetzen. Im Alltag wird property als Decorator @property verwendet.
Einleitung
In Sprachen wie Java schreibt man getName() und setName() als Methoden — Konsumenten müssen wissen, dass es Methoden sind. Python kennt eine sauberere Lösung: Properties. obj.attr und obj.attr = value sehen aus wie Attribut-Zugriffe, rufen aber unter der Haube Funktionen auf.
Drei Hauptanwendungen:
- Computed Properties:
circle.areaausradiusberechnen. - Validierung beim Setzen: Nur erlaubte Werte zulassen.
- Lazy-Loading: Teures Berechnen erst beim ersten Zugriff.
property() lässt sich in zwei Formen nutzen:
- als Decorator
@property(Standard, lesbar) - als Funktionsaufruf
prop = property(fget, fset, fdel, doc)(für Metaklassen, dynamisch)
Syntax
# Decorator-Form (Standard)
class MyClass:
@property
def attr(self):
...
@attr.setter
def attr(self, value):
...
@attr.deleter
def attr(self):
...
# Funktions-Form
attr = property(fget=None, fset=None, fdel=None, doc=None)fget Getter-Funktion. Bekommt self.
fset (Optional) Setter-Funktion. Bekommt self und value.
fdel (Optional) Deleter-Funktion. Bekommt self.
doc (Optional) Docstring.
Rückgabewert
Ein property-Descriptor — das Objekt, das beim Attribut-Zugriff Getter/Setter/Deleter triggert.
Beispiele
Computed Property (read-only)
Klassischer Anwendungsfall — area und circumference werden bei jedem Zugriff aus radius berechnet:
from math import pi
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return pi * self.radius ** 2
@property
def circumference(self):
return 2 * pi * self.radius
c = Circle(5)
print(round(c.area, 2))
print(round(c.circumference, 2))78.54
31.42Mit Setter und Validierung
Mit Setter lässt sich beim Schreiben validieren — der Konsument bemerkt nichts vom Property-Mechanismus:
class Temperature:
def __init__(self, celsius=0):
self.celsius = celsius # ruft Setter
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Unter dem absoluten Nullpunkt!")
self._celsius = value
t = Temperature(25)
print(t.celsius)
t.celsius = -10
print(t.celsius)
try:
t.celsius = -300
except ValueError as e:
print(e)25
-10
Unter dem absoluten Nullpunkt!Mit Deleter
Selten gebraucht, aber praktisch — der Deleter kann Aufräumarbeit machen:
class Cache:
def __init__(self):
self._data = {"key": "value"}
@property
def data(self):
return self._data
@data.deleter
def data(self):
print("Cache wird geleert")
self._data.clear()
c = Cache()
print(c.data)
del c.data
print(c.data){'key': 'value'}
Cache wird geleert
{}Praktische Beispiele
Lazy-Loading mit Property
Teure Berechnungen nur einmal — beim ersten Zugriff cachen, danach das gespeicherte Ergebnis liefern:
class Report:
def __init__(self, raw_data):
self.raw_data = raw_data
self._summary = None
@property
def summary(self):
if self._summary is None:
print("(berechne summary ...)")
self._summary = sum(self.raw_data)
return self._summary
r = Report([1, 2, 3, 4, 5])
print(r.summary)
print(r.summary) # cached(berechne summary ...)
15
15functools.cached_property
In Python 3.8+ gibt es eine spezielle Decorator-Variante für genau diesen Pattern — kein manuelles Caching nötig:
from functools import cached_property
class Report:
def __init__(self, raw_data):
self.raw_data = raw_data
@cached_property
def summary(self):
print("(berechne ...)")
return sum(self.raw_data)
r = Report([1, 2, 3])
print(r.summary)
print(r.summary) # nicht erneut berechnet(berechne ...)
6
6Praktische Hinweise
- Sauberer als
get_*/set_*-Methoden — und konsumenten-transparent. - Read-only: Nur
@property-Getter ohne Setter macht ein Read-only-Attribut. - Convention: das interne Backing-Feld bekommt ein führendes
_(self._celsius). functools.cached_propertyfür Lazy-One-Time-Berechnungen.- Verwandte Konzepte: Descriptors,
__get__/__set__/__delete__,dataclass(field).