Getters with @property
A method that gives the value of a private attribute is called a getter, and a method that changes it a setter. With ordinary methods, the client has to write heater.get_target(), with parentheses. Python has a neater way: a property.
The @property decorator
A line that starts with @, just above a def, is a decorator. It changes how the method is used. With the decorator @property, the method is used like an attribute, without parentheses:
class Thermostat:
def __init__(self):
self.__target = 20
@property
def target(self):
return self.__target
heater = Thermostat()
print(heater.target)
20The getter is named after the attribute, without the underscores. The client writes heater.target as if it were an attribute, but what runs is the method, which returns the private __target.
A property with only a getter is read-only. Trying to assign to it is an error:
class Thermostat:
def __init__(self):
self.__target = 20
@property
def target(self):
return self.__target
heater = Thermostat()
heater.target = 45
Traceback (most recent call last):
File "main.py", line 11, in <module>
heater.target = 45
^^^^^^^^^^^^^
AttributeError: property 'target' of 'Thermostat' object has no setterThe next step adds a setter, so the client can change the target too.
Properties that are calculated
A getter doesn't need an attribute of its own. It can work out its value every time it's asked:
class Temperature:
def __init__(self, celsius):
self.__celsius = celsius
@property
def celsius(self):
return self.__celsius
@property
def fahrenheit(self):
return self.__celsius * 9 / 5 + 32
today = Temperature(25)
print(today.celsius)
print(today.fahrenheit)
25
77.0The object stores only degrees Celsius, but the client can't tell celsius from fahrenheit: both look like attributes. That's encapsulation at work. The class decides what it stores, and could change that later without the client noticing.
No parentheses
A property is used without parentheses. With them, Python first gets the value (here the number 20) and then tries to call that number like a function:
class Thermostat:
def __init__(self):
self.__target = 20
@property
def target(self):
return self.__target
heater = Thermostat()
print(heater.target())
Traceback (most recent call last):
File "main.py", line 11, in <module>
print(heater.target())
~~~~~~~~~~~~~^^
TypeError: 'int' object is not callable