I was tinkering with Python the other day when I encountered the “property” keyword. I had seen it many times before and I sort of knew what it does, but I never really had a chance to dig deep into it. As it turns out, “property” in Python is very useful when you are designing large systems. People who come from the world of object oriented programming will appreciate it right away. It’s a nifty little concept which makes the life of a programmer much simpler. So what is it all about? Before we jump directly into what it is, let’s get some perspective as to why we would need it in the first place.
Why do we need it?
Let’s say that we decide to create a class that could store your height in centimeters. Internally, we would like the class to implement a method to convert centimeters into inches. This is a pretty straightforward case and we would do something like this:
class PeopleHeight: def __init__(self, height = 150): self.height = height def convert_to_inches(self): return (self.height * 0.3937)
As we can see, we have a class variable named “height” and we have a method that converts it to inches. We can now make objects out of this class and manipulate the attribute “height” as we like. Just go into the Python shell and do the following (you can go into the Python shell by typing “python” from your terminal):
Create a new object:
>>> person = PeopleHeight()
Set the height attribute:
>>> person.height = 182
Get the value of the height attribute
>>> person.height 182
Convert the height to inches
>>> person.convert_to_inches() 71.6534
An interesting thing to note here is that whenever we assign or retrieve any object attribute, like “height” in our case, Python searches it in the object’s __dict__ dictionary.
>>> person.__dict__ {'height': 182}
Internally, person.height becomes person.__dict__[‘height’]. So far, so good? We don’t really see why we would need “property” just yet, right? Okay, let’s move along then.
Let’s say that other people start using this class in their programs. They just inherit your class, create a child class and do all sorts of things. Whenever you are working in a team, people like to create class hierarchies and build software systems. Alright, so coming back to our case, other programmers inherited your class and did all kinds of assignments to the object. This seems fine up until now. Now that things are getting serious, somebody comes and suggests that we need to make the parent class robust, and that the value of “height” cannot go below 0. This is not an unfair demand, given that people cannot have negative height. So now, we have to implement this constraint in our class. So we can just update the class and release a new version.
Where’s the encapsulation?
Object oriented programmers might cringe at the thought of providing direct access to class variables. So a good solution to the above problem will be to hide the attribute i.e. make it private, and define new getter and setter interfaces to manipulate it. This can be done as follows.
class PeopleHeight: def __init__(self, height = 150): self.set_height(height) def convert_to_inches(self): return (self.get_height() * 0.3937) # new getter method def get_height(self): return self._height # new setter method def set_height(self, value): if value < 0: raise ValueError("Height cannot be negative") self._height = value
As we can see here, the new methods get_height() and set_height() are defined and height has been replaced with _height. An underscore (_) at the beginning is used to denote private variables in Python. Technically speaking, there is no concept of “private variables” in Python. It’s just a convention to use underscore to denote that it’s private. We can still access it and modify it from outside. It works more on an honor system than anything else!
>>> p = PeopleHeight(-80) Traceback (most recent call last): ... ValueError: Height cannot be negative >>> p = PeopleHeight(164) >>> c.get_height() 164 >>> c.set_height(173) >>> c.set_height(-159) Traceback (most recent call last): ... ValueError: Height cannot be negative
This update successfully implemented the new restriction. Although we are no longer allowed to explicitly set the height below 0, we can still do this:
>>> p._height = -147 >>> c.get_height() -147
We accessed the private variable and changed its value to a negative number. This is what we were talking about earlier. Python doesn’t impose that restriction. Wait a minute, doesn’t that defeat the whole purpose? Well, be that as it may, it’s not of great concern to us at the moment. We will cross that bridge when we get to it! The bigger problem at hand here that the above update affects all those programmers who used this as their base class to build their classes. Now, they have to modify their code from obj.height to obj.get_height() and all assignments like obj.height = val to obj.set_height(val). This kind of refactoring seems like a lot of work, especially when you have tens of thousands of lines of code!
So basically, what I’m getting at is that our new update is not backward compatible. This is where the concept of property comes to our rescue.
The properties of Property
Let’s get Pythonic from here on. A good way to deal with the above problem is to use property. Here is how we can do it:
class PeopleHeight: def __init__(self, height = 150): self.height = height def convert_to_inches(self): return (self.height * 0.3937) def get_height(self): print("Inside the getter method") return self._height def set_height(self, value): if value < 0: raise ValueError("Height cannot be negative") print("Inside the setter method") self._height = value height = property(get_height, set_height)
The print() statements inside get_height() and set_height() help us observe what methods are being executed. The last line of the code makes a property object “height”. Simply put, property attaches some code (get_height and set_height) to the member attribute access (height). Any code that retrieves the value of height will automatically call get_height() instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to height will automatically call set_height(). This is a cool feature in Python.
>>> p = PeopleHeight() Inside the setter method
We can see above that set_height() was called even when we created an object. Can you guess why? The reason is that when an object is created, __init__() method gets called. This method has the line self.height = height. This assignment automatically called set_height(). Isn’t that nice?
>>> p.height Inside the getter method 150
Similarly, any access like p.height automatically calls get_height(). This is what property does. Here are a few more examples.
>>> p.height = 177 Inside the setter method >>> p.convert_to_inches() Inside the getter method 69.6849
By using property, we modified our class and implemented the value constraint without needing any changes to the client code. Thus our implementation was backward compatible and everybody is happy. Finally, note that the actual height value is stored in the private variable _height. The attribute height is a property object which provides interface to this private variable.
Is there more to Property?
In Python, property() is a built-in function that creates and returns a property object. So go ahead and type the following in the Python shell to see the property object:
>>> property() <property object at 0x107ad2890>
The signature of this function is given below:
property(fget=None, fset=None, fdel=None, doc=None)
where, fget is a function to get the value of the attribute, fset is a function to set the value of the attribute, fdel is a function to delete the attribute and doc is a string (like a comment). As seen from the implementation, these function arguments are optional. A property object has three methods, getter(), setter(), and delete() to specify fget, fset and fdel at a later point. Consider the following line:
height = property(get_height, set_height)
This could have been broken down as:
height = property() height = height.getter(get_height) height = height.setter(set_height)
The above two snippets of codes are equivalent. If you are familiar with decorators in Python, you can see that the above construct can be implemented as decorators. Wouldn’t it be nice if we don’t have to define names like get_height and set_height? I mean, they are unnecessary and they pollute the class namespace. So to address this issue, we reuse the name “height” while defining our getter and setter functions. This is how it can be done.
class PeopleHeight: def __init__(self, height = 150): self._height = height def convert_to_inches(self): return (self.height * 0.3937) @property def height(self): print("Getting the value of height") return self._height @height.setter def height(self, value): if value < 0: raise ValueError("Height cannot be negative") print("Setting the value of height") self._height = value
The above implementation is the recommended way to make properties. It’s simple too! You will most likely encounter these types of constructs when looking for property in Python.
——————————————————————————————————––
Honestly I couldn’t find a better explanation than this.
Thanks 🙂
I’ve been searching for quite a while, this is the best tutorial on python’s Property. Thank you.
Wow… I have read roughly a billion explanations on properties from what is available using google, and none of them explained the subject very well… At all. Just setters this, getters that. Don’t use them, blah blah blah. I was starting to think that maybe I was not meant to understand them, but I was able to follow your article with no trouble at all. and now I fully comprehend them.
Thanks, I will be sure to check out your other articles, I hope to find some other useful stuff I may or may not be struggling with!
This was an amazing explanation. I couldn’t wrap my head around properties until I read this. Thank you so much!
Thank you for this informative read, I have shared
it on Facebook.