Python's @property-decorator beheersen: schone, backward-compatibele getters & setters
Python's @property-decorator beheersen
Ontdek hoe Python's ingebouwde @property-decorator je laat schrijven beknopte, onderhoudbare getters en setters terwijl je API backward-compatibel blijft.
Python's @property-decorator is een krachtig hulpmiddel voor het encapsuleren van attribuuttoegang. Het stelt je in staat om custom logica te definiëren die draait wanneer een attribuut gelezen of geschreven wordt, zonder aanroepers hun code te laten wijzigen. In de volgende secties lopen we door een real-world voorbeeld – temperatuurconversies – tonen hoe je constraints afdwingt, en leggen uit waarom @property de idomatische oplossing is.
Een klasse zonder getters en setters
Stel we hebben een simpele klasse nodig die temperatuur in Celsius opslaat en kan converteren naar Fahrenheit:
class Celsius:
def __init__(self, temperatuur=0):
self.temperatuur = temperatuur
def naar_fahrenheit(self):
return (self.temperatuur * 1.8) + 32
Gebruik is eenvoudig:
# Maak nieuw object
mens = Celsius()
# Zet temperatuur
mens.temperatuur = 37
# Lees temperatuur
print(mens.temperatuur)
# Converteer naar Fahrenheit
print(mens.naar_fahrenheit())
Uitvoer
37 98.60000000000001
Het drijvende-komma resultaat is verwacht door binaire representatie van decimalen. Wanneer je mens.__dict__ inspecteer, zie je dat Python de waarde in een gewone dictionary-entry opslaat:
>>> mens.__dict__
{'temperatuur': 37}
Constraints toevoegen met getters en setters
Real-world objects hebben vaak validatie nodig. Bijvoorbeeld, temperaturen kunnen niet onder absolute nul (-273.15 °C) dalen. Een directe manier is het attribuut te verbergen en expliciete getter/setter-methoden te exposeren:
class Celsius:
def __init__(self, temperatuur=0):
self.set_temperatuur(temperatuur)
def naar_fahrenheit(self):
return (self.get_temperatuur() * 1.8) + 32
# getter
def get_temperatuur(self):
return self._temperatuur
# setter
def set_temperatuur(self, waarde):
if waarde < -273.15:
raise ValueError("Temperatuur onder -273.15 is niet mogelijk.")
self._temperatuur = waarde
Nu dwingt de klasse de ondergrens af, maar bestaande code die obj.temperatuur benaderde, moet geüpdatet worden naar obj.get_temperatuur() en obj.set_temperatuur(waarde). Dat is een grote refactor voor grote codebases.
Bovendien zijn Python's "private" variabelen slechts een naamconventie – _temperatuur kan nog steeds direct benaderd worden, wat encapsulatie breekt. Hier schittert @property.
De property-klasse gebruiken
Python biedt een property-klasse die getter- en setter-functies koppelt aan een attribuutnaam:
class Celsius:
def __init__(self, temperatuur=0):
self.temperatuur = temperatuur
def naar_fahrenheit(self):
return (self.temperatuur * 1.8) + 32
# getter
def get_temperatuur(self):
print("Waarde ophalen...")
return self._temperatuur
# setter
def set_temperatuur(self, waarde):
print("Waarde instellen...")
if waarde < -273.15:
raise ValueError("Temperatuur onder -273.15 is niet mogelijk")
self._temperatuur = waarde
# exposeren als property
temperatuur = property(get_temperatuur, set_temperatuur)
Met deze setup gedraagt de volgende code zich precies als de oorspronkelijke simpele klasse, maar met validatie achter de schermen:
mens = Celsius(37)
print(mens.temperatuur) # triggert getter
print(mens.naar_fahrenheit()) # gebruikt getter intern
mens.temperatuur = -300 # triggert setter en gooit ValueError
Uitvoer
Waarde instellen... Waarde ophalen... 37 Waarde ophalen... 98.60000000000001 Waarde instellen... Traceback (most recent call last): ... ValueError: Temperatuur onder -273 is niet mogelijk
Let op hoe de code die temperatuur benadert ongewijzigd blijft; alleen de onderliggende implementatie kreeg validatie.
Tip: De echte data zit in _temperatuur; temperatuur is slechts een handige, veilige interface.
De @property-decorator
Hoewel de property-klasse werkt, biedt Python's decorator-syntax een schonere, leesbaardere manier om hetzelfde te bereiken. De decorator creëert automatisch het property-object en koppelt de getter en setter aan de attribuutnaam.
class Celsius:
def __init__(self, temperatuur=0):
self.temperatuur = temperatuur
def naar_fahrenheit(self):
return (self.temperatuur * 1.8) + 32
@property
def temperatuur(self):
print("Waarde ophalen...")
return self._temperatuur
@temperatuur.setter
def temperatuur(self, waarde):
print("Waarde instellen...")
if waarde < -273.15:
raise ValueError("Temperatuur onder -273 is niet mogelijk")
self._temperatuur = waarde
Gebruik blijft hetzelfde:
mens = Celsius(37)
print(mens.temperatuur)
print(mens.naar_fahrenheit())
# Poging om ongeldige temperatuur in te stellen
try:
Celsius(-300)
except ValueError as e:
print(e)
Uitvoer
Waarde instellen... Waarde ophalen... 37 Waarde ophalen... 98.60000000000001 Waarde instellen... Traceback (most recent call last): ... ValueError: Temperatuur onder -273 is niet mogelijk
@property gebruiken houdt de naamruimte netjes, elimineert de noodzaak voor aparte getter/setter-methodenamen, en is het aanbevolen patroon voor de meeste Python-ontwikkelaars.
Python
- Python Datatypes onder de knie krijgen: Een praktische gids
- Python Operators onder de knie krijgen: Een uitgebreide gids
- Python Pass Statement onder de knie krijgen: Een praktische gids
- Python Functie-argumenten onder de knie krijgen: Positionele, Keyword en Default Parameters
- Python-dictionaries beheersen: aanmaken, manipuleren en geavanceerde technieken
- Python-erfelijkheid beheersen: concepten, syntax en praktische voorbeelden
- Operator overloading in Python beheersen: een praktische gids
- Python-iterators: hoe ze werken en hoe je je eigen maakt
- Python-closures uitgelegd: hoe geneste functies variabelen vangen
- Python's @property-decorator beheersen: schone, backward-compatibele getters & setters
- Python datetime beheersen: praktische gids voor datums, tijden en tijdzones