5 minutes
Python property class and decorator
About 2 months ago I considered my self an intermediate python user.
This level of knowledge was enough to work on pandas data transformation and use most of the data science API.
However, when it comes to software engineering and developing production, tested code I realized that there was a lot to learn about programming concepts and how that would work with python.
My intermediate knowledge and experience already gave me lots of power but when I started to dig into source code and also at work when I had to browse to some large code base I felt that I did not know lots of things in python and had to get my s*** together and become a proficient advanced and fluent python user.
I am talking about advanced concepts like property, decorator, inheritance, mro, getters, setters and the other OOP paradigm like interfaces and encapsulation that was necessary to up my game.
In this post I will give an introduction of the property class and property decorator.
When someone start learning about classes one of the first concepts we learn is class attribute and instance attribute which is not the scope of this post. I assume that this is already a clear concept. If not you can refresh here
The property class helps to manage class attribute where you can control the mutability of an instance property and also set some conditions you want an instance attribute to have.
A classical example which is widely used in many example is temperature.
Say was want to create a class that manages objects temperature and convert between Celsius and Fahrenheit.
The absolute zero temperature is -273 Celsius and you would not want to allow anyone to set a temperature below this value.
A way to manage this is via property.
There are two ways you can do it, i.e. using the property
class itself or the @property
decorator.
The documentation doc string in python is very good actually. Lets print it.
help(property)
Here is an exert of it with a quick example to get you started.
class C(object):
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
The property class take 4 arguments and returns the property attribute
property(fget=None, fset=None, fdel=None , doc=None) -> property attribute
fget
is a function to be used for getting an attribute value, and likewise
fset
is a function for setting
and fdel
a function for deleting,
an attribute.
The decorator alternative makes it simpler
class C(object):
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
This where just template examples. Lets now write our temperature class that I took from programviz
# Using @property decorator
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
@property
def temperature(self):
print("Getting value...")
return self._temperature
@temperature.setter
def temperature(self, value):
print("Setting value...")
if value < -273.15:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value
# create an object
human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
coldest_thing = Celsius(-300) # this is going to raise an exception as it is below 273.15
# output of the program (commented out)
> Setting value...
> Getting value...
> 37
> Getting value...
> 98.60000000000001
> Setting value...
Traceback (most recent call last):
File "temperature_.py", line 28, in <module>
coldest_thing = Celsius(-300)
File "temperature_.py", line 3, in __init__
self.temperature = temperature
File "temperature_.py", line 17, in temperature
raise ValueError("Temperature below -273 is not possible")
ValueError: Temperature below -273 is not possible
Note that when an instance of class initialize it firsts sets the value then it gets.
I got confused with this as sometimes I used the property to set an immutable property but on the example it made it mutable with the @temperature.setter
decorator.
If you pay attention to the example on the documentation it does not initiate with a default value and the x
. So let’s implement it as in the help example
class C(object):
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
c = C(3)
print(c.x)
# output
Traceback (most recent call last):
File "c.py", line 19, in <module>
print(c.x)
File "c.py", line 6, in x
return self._x
AttributeError: 'C' object has no attribute '_x'
So if we add the __init__
class C(object):
def __init__(self, x=0):
self.x = x
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
c = C(3)
print(c.x)
c.x = 23
print(c.x)
# output
>0
>23
Actually what happened is that when it __init__
the instance it set the value to the default value.
If you want to make it immutable like having a value that does not change just only implement set method.
Let’s demonstrate it
class C(object):
def __init__(self):
self._x = 42
@property
def x(self):
"I am the 'x' property."
return self._x
c = C()
print(c.x)
c.x = 23
# output
>42
Traceback (most recent call last):
File "c.py", line 24, in <module>
c.x = 23
AttributeError: can't set attribute
Note that this time on the __init__
I set the value 42
to the _x
underscore version to set it to a value at initialization and never allow it to change.
I think this is it for property.
If you inspect source code in GitHub you will see it a lot and now you know what is going on when you see it.