Understanding Python “Property”

1 mainI 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.

——————————————————————————————————–

2 thoughts on “Understanding Python “Property”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s