For a long time, I’ve not been able to use classes well enough in Python.
Rare resources on the web could help me write classes that are Pythonic. That’s because a lot of resources trying to overengineer Python classes.
I used to write methods starting with get_
and set_
to get and
set attributes in Python. This practice was similar to the way Java
developers write their getters and setters. Ugly and not Pythonic.
Until I knew that there is a better way to do this in a Python way.
In this post, I’ll show you how to write @property decorator in Python classes to be Pythonic. It should be easy to understand and use, especially with the step-by-step approach.
The @property getter
Let’s first simulate our use case as if @property does not exist.
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
def get_fahrenheit(self):
return (self._celsius * 9 / 5) + 32
temperature = Temperature(37)
print(temperature.get_fahrenheit())
# 98.6
In the above example, a class Temperature
has a getter method called
get_fahrenheit
. It returns the temperature to Fahrenheit.
This is a very typical use case of a getter method.
Now, Fahrenheit looks like a property of a class. You don’t need to call it a function like the following:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def fahrenheit(self):
return (self._celsius * 9 / 5) + 32
temperature = Temperature(37)
print(temperature.fahrenheit)
# 98.6
We can see that we got rid of the ()
in the getter method call.
Instead, we now have a @property
decorator before fahrenheit
function. This function acts as a getter method.
As a side effect, we no longer need to prepend get_
to the function
name.
Note that the signature of the getter method needs to be (self)
.
Why do we use @property decorator?
In the previous example, we have a property decorator. This is a
decorator we use to add a getter method to a class. We could use the property
function instead like this:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
def get_fahrenheit(self):
return (self._celsius * 9 / 5) + 32
fahrenhei = property(fget=get_fahrenheit)
temperature = Temperature(37)
print(temperature.fahrenhei)
# 98.6
but of course, the @property
decorator syntax is easier.
The @property setter
But the property is not only useful for that. We can also use it to set
and delete the value of the attribute. Let’s see how we can set a new
value for the _celsius
attribute:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def fahrenheit(self):
return (self._celsius * 9 / 5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
if not isinstance(value, int) and not isinstance(value, float):
raise TypeError('value must be a number')
self._celsius = (value - 32) * 5 / 9
temp = Temperature(98)
print(temp._celsius)
# 98
temp.fahrenheit = 98.6
print(temp._celsius)
# 37.0
# calling the getter
print(temp.fahrenheit)
# 98.6
temp.fahrenheit = "98F"
print(temp._celsius)
# TypeError: Value must be an integer
The property decorator, acting as a setter method, is useful especially when we want to:
- Do validation to the value of the attribute.
- Do some pre-processing. before the setting happens.
In the above example, we validated the value of the _celsius
attribute. We then checked if it’s not an integer nor float, we raise a
TypeError
.
Noe that the name of the decorator (before .setter
) must be
identical to the method name. In our case, it’s fahrenheit
.
Also note that the signature of the setter method needs to be (self, value)
. The name of the value is arbitrary.
Conclusion
We’ve seen how to write a getter and a setter method in Python classes using a property decorator. The feature that saves us time and helps write Pythonic and cleaner code.