If we are using Encapsulation to manage our object attributes, we can also use getters and setters to manage them - especially if the attribute is private. For example:
class Wallet:
def __init__(self):
self.__money = 0We can define getter and setter methods for accessing the private attribute __money using the @property decorator:
class Wallet:
def __init__(self):
self.__money = 0
# A getter method
@property
def money(self):
return self.__money
# A setter method
@money.setter
def money(self, money):
if money >= 0:
self.__money = moneyBy using the @property decorator (and the @money.setter in the second definition) a client can access the __money attribute, but with some safeguards. This means that a client will not know that the attribute is private, either.
wallet = Wallet()
print(wallet.money)
wallet.money = 50
print(wallet.money)
wallet.money = -30
print(wallet.money)Note
Parentheses are not necessary; instead it is perfectly acceptable to state
wallet.money = 50, as if we were simply assigning a value to a variable. Indeed, the purpose was to hide (i.e. encapsulate) the internal implementation of the attribute while offering an easy way of accessing and modifying the data stored in the object.
Note
The getter method, i.e. the
@propertydecorator, must be introduced before the setter method, or there will be an error when the class is executed. This is because the@propertydecorator defines the name of the “attribute” offerred to the client. The setter method, added with.setter, simply adds a new functionality to it.
Let’s assume this setup:
class SimpleDate:
def __init__(self, day: int):
self.day = day # <--- this line is the key
@property
def day(self):
return self.__day
@day.setter
def day(self, day: int):
print("Setter called") # Just for illustration
if day < 1 or day > 30:
raise ValueError("Invalid day")
self.__day = dayNow when you do:
date = SimpleDate(10)The steps are:
-
__init__runs. -
Inside
__init__, the lineself.day = dayexecutes. -
Python sees that
self.day = ...refers to a property with a setter. -
Instead of assigning to an instance variable, it calls:
SimpleDate.day.__set__(self, 10)Or simply:
self.day(10) # the setter -
Inside the setter, the value is validated, and then the actual data is stored in the backing variable:
self.__day = day # safe, no recursion
So: self.day = day calls the @day.setter.
❌ What Causes Recursion?
Now, imagine if your setter did this:
@day.setter
def day(self, day: int):
self.day = day # ← This calls the setter again!-
You just called the setter from inside itself → it runs again…
-
Which calls itself again…
-
And so on → infinite recursion →
RecursionError.
✅ Correct Pattern
Always set the backing variable, not the property:
@day.setter
def day(self, day: int):
self.__day = day # Safe: sets the actual storage, not the property