Na dit hoofdstuk kun je:
- attributen “Pythonic” afschermen met
@property, - validatie toevoegen zonder dat je API verandert,
- read-only en computed properties maken,
- en je kent de valkuilen (recursie, te veel magie, verkeerde plek voor logica).
1) Waarom @property in Python? #
In veel talen maak je expliciet getters/setters: get_name(), set_name(...) om een variabele op te halen of weg te schrijven.
In Python wil je meestal dat gebruikers gewoon:
user.namePythonkunnen doen.
Maar soms wil je óók:
- validatie (geen negatieve leeftijd),
- read-only gedrag,
- computed values,
- of later intern refactoren zonder code van gebruikers te breken.
Daar is @property perfect voor.
2. Basis: van “open attribuut” naar property #
Zonder property (simpel maar geen controle) #
class Product:
def __init__(self, price):
self.price = pricePythonProbleem: price kan negatief gezet worden:
p = Product(10)
p.price = -999 # geen blokkadePythonMet property (controle + zelfde gebruik) #
class Product:
def __init__(self, price):
self.price = price # gaat via de setter
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self._price = valuePythonAls je Product(10) doet, dan voert __init__ uit:
self.price = price
Dat is niet “zet het attribuut direct”, maar: Python ziet datpriceeen property is → en roept dusprice.setteraan.
Dus initialisatie gebruikt dezelfde regels als later aanpassen. Dat is handig.
Gebruik blijft hetzelfde:
p = Product(10)
p.price = 12
print(p.price)PythonEn hierdoor worden dus de getter en setters aangeroepen, waardoor je bij het ophalen of wegschrijven specifieke acties (code) kan toevoegen.
Waarom niet direct self._price = price?
Dat kan ook, maar dan bypass je je eigen validatie:
def __init__(self, price):
self._price = price # slaat setter overPythonGevolg: Product(-5) zou dan gewoon kunnen bestaan, terwijl je setter dat eigenlijk wil verbieden.
Dus let op: intern gebruiken we _price als “private-ish” opslag.
_price is een conventie voor “intern gebruik” #
In Python kun je niet echt een veld echt privé maken zoals in sommige andere talen. Daarom gebruiken Python-programmeurs een naamconventie:
price= de publieke interface (wat andere code “mag” gebruiken)_price= intern attribuut (“gebruik dit liever niet direct van buitenaf”)
De underscore _ betekent dus: dit is bedoeld voor intern gebruik binnen de class (of module). Het is geen harde beveiliging, maar een duidelijke afspraak.
Is _price echt privé?
Niet echt, je kunt dit nog steeds doen:
p = Product(10)
p._price = -999 # kan, maar is “not recommended”PythonHet punt is: je hoort het niet te doen. Als je het tóch doet, omzeil je bewust de regels/validatie.
En hoe zit het met “echte private” in Python?
Er is ook __price (dubbele underscore). Dat triggert name mangling (Python hernoemt het intern), waardoor het lastiger is om van buitenaf te gebruiken. Maar:
- het is nog steeds niet “security”,
- en vaak onnodig zwaar voor gewone code.
Meestal is _price de beste balans: duidelijk, simpel, Pythonic.
Als je wil kan ik ook een klein voorbeeld geven van __price (name mangling) en wanneer dat nuttig is.
3. Read-only properties #
Soms wil je iets wel kunnen lezen, maar niet zomaar overschrijven.
Bijvoorbeeld: een id die eenmaal gezet is.
class User:
def __init__(self, user_id, name):
self._id = user_id
self.name = name
@property
def id(self):
return self._idPythonNu kan:
u = User(123, "Alice")
print(u.id) # 123
u.id = 999 # AttributeError: can't set attributePythonDat is precies wat je wil: de buitenwereld kan het niet kapot maken.
4. Computed properties (afgeleid van andere data) #
Properties zijn top voor waarden die je niet opslaat, maar berekent.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.heightPythonGebruik:
r = Rectangle(3, 4)
print(r.area) # 12PythonVoordeel: je hoeft area niet “bij te houden” wanneer width/height veranderen.
5. Validatie + invarianten netjes afdwingen #
Voorbeelden van regels die objecten vaak moeten bewaken:
- leeftijd ≥ 0
- percentage tussen 0 en 1
- status is één van een set
- string mag niet leeg zijn
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = valuePython6. Veelgemaakte property-fouten #
Fout 1: oneindige recursie (klassieker) #
Dit gaat mis:
class Product:
@property
def price(self):
return self.price # ❌ roept zichzelf weer aanPythongoed:
return self._pricePythonFout 2: setter die zichzelf aanroept #
@price.setter
def price(self, value):
self.price = value # ❌ oneindige loopPythonGoed:
self._price = valuePythonFout 3: te veel logica in properties
Een property hoort meestal: #
- iets te lezen,
- licht te valideren,
- of iets af te leiden.
Maar: een property die zware I/O doet (API call, database query) is vaak verwarrend:
user.profile # en ineens een netwerkrequest?!PythonBeter: expliciete methode load_profile() of fetch_profile().
7. Wanneer gebruik je géén @property? #
- Als een attribuut simpel is en geen regels nodig heeft → laat het open.
- Als je eigenlijk een actie uitvoert → maak een methode.
account.balance(property) ✅account.withdraw(50)(methode) ✅account.balance = account.balance - 50(outside) ❌ (breekt encapsulation)
Nu je getters en setters hebt geleerd is het belangrijk dat je het concept van overerving snapt om goed object georiënteerd te kunnen programmeren.

