Python @Property Decorator

In this tutorial, you will gain an understanding of @property decorator, when to and how to use @property decorator in python with the help of simple and easy examples.
From our previous tutorial, we know that the main purpose of a decorator is to modify the behavior of a function or a class attribute in such a way that the user of the class doesn't have to make any changes. The decorators we dealt with in the last tutorial were user-defined decorators. To make the decoration, even more, easier python has got three basic built-in decorators like @classmethod, @staticmethod and @property method.  Here in this tutorial, we will focus on Python @ property decorators. 

What is a @property decorator? 

The @property decorator is basically a built-in decorator whose one of the main purposes is to use a class method as an attribute despite being a method with parentheses ().

Let's try to understand this by creating a class named License. Our License class contains name, age, and data as its attributes.


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.age=age
        self.data = '{} is {} years old'.format(self.name,self.age) 

 

Now we have created the class and the next step is that we want to create an instance for the class License. Here L i s the instance  with two values and with the help of a dot operator(.) we can access the values of attributes in the class as shown  below.

Now we have created the class and the next step is that we want to create an instance for the class License. Here L i s the instance with two values and with the help of a dot operator(.) we can access the values of attributes in the class as shown below.


L = License('Tom',25)
print(L.name)
print(L.age)
print(L.data)
 

Output:


Tom
25
Tom is 25 years old

When you observe the code snippet you can see that self.name and self.age are primary attributes while self.data is a derived attribute.

When to use @ property 

Suppose that at some stage you wish to change the value of any of the attributes in the class anticipating the changes you made will reflect in the program wherever you use the attribute(which is changed). But in reality, this does not occur.

In our case let’s say you wish to change the age after instantiation and to do that we assign L.age with 15  as shown below.


L = License('Tom',25)
print(L.name)

L.age = 15  #assigned new age
print(L.age)
print(L.data)
 

Actually, the expected output will be something like this: L.age as 15 and L.data as Tom is 15 years old. But the output we received is :


Tom
15
Tom is 25 years old

Here, you can see the age attribute changes to 15. But the age in self. data attribute remains the same without being updated. 
The reason behind this issue is  In class if you change the value of an attribute, the other attributes which are derived from that class won't automatically update.

So the chance to break the code is high if changes made to the class won’t automatically update in their derived class. 

So how can we fix this problem?

Attribute to Method Conversion

One way to solve this problem is by converting the self.data attribute in the class to a method, getdata() as shown in the below example.


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.age=age
         
    def getdata(self):
        return '{} is {} years old'.format(self.name,self.age )
 

Now the class has been modified by removing the attribute and adding a new method. So in effect, our class now has no attribute named self.data in spite, we have a method getdata().

Since getdata() is a method we have to be careful while calling this method. Because if we call the method without parentheses as shown below, we will end up in attribute error.
 


L = License('Tom',25)   # Instance 
print(L.name)

L.age = 15
print(L.age)
print(L.getdata)   # missing parantheses,getdata is a method not an attribute

 

The output you receive will be like:


Tom
15
<bound method License.getdata of <__main__.License object at 0x038CB580>>

So the whole program should be like this :


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.age=age
         
    def getdata(self):
        return '{} is {} years old'.format(self.name,self.age )
L = License('Tom',25)
print(L.name)

L.age = 15
print(L.age)
print(L.getdata())  #calling method

 

Output:


Tom
15
Tom is 15 years old

In a nutshell, all the programs that implement the above class have to alter their code wherever they are encountering self.data with getdata() . If the client-side code has hundreds and thousands of lines then it would definitely be troublesome.

Solving using @property decorator

So the perfect solution that python offers is to convert the method as a property. This can be achieved by adding a @property decorator prior to the method’s definition which allows using of the method as an attribute. As a result that, the getdata() method can be accessed as an attribute in place of the method with (). The following example illustrates the use of @property.

 


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.age=age

    @property 
    def getdata(self):
        return '{} is {} years old'.format(self.name,self.age )

    

L = License('Tom',25)
print(L.name)

L.age = 15
print(L.age)
print(L.getdata)  #calling method as attribute

 

This code snippet will give you the same output but here you can see that we have removed parentheses () while calling the method decorated with @propertyi.e getdata(). Because by using @property the getdata() method is accessed as an attribute thus enabling our code as loosely coupled with the client code. The below sequence in the above code is generally known as the getter method which is used to retrieve the value in an attribute .


@property 
    def getdata(self):
        return '{} is {} years old'.format(self.name,self.age )
 

The Setter Method

Like the getter method, in python, we have a setter method that is used to set values to an attribute in class thereby ensuring the principle of encapsulation of data.  
While defining a setter method you need to follow some rules which are listed below:

  • The syntax of setter method is @{methodname}.setter
  • The name of both the setter and getter method should be the same
  • The value that is set by the user is always accepted as an argument in the setter method. 

Imagine that our client wants to change his age as he reaches 18 and to achieve this need from the client-side we need to inculcate setter method property to our class thereby helping the client unknowingly. Our program will change too as follows:


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.__age=age

    @property 
    def getdata(self):
        return self.name,self.__age

    @getdata.setter
    def getdata(self,age):
        if age < 18:
            print('Sorry {}, You are ineligible for License.'.format(self.name))
            self.__age=age
        else:
            print('Congrats {},You are eligible for License'.format(self.name))
            self.__age=age
               

L1 = License('Tom',25)
print('Before setting',L1.getdata)

L1.getdata=15 #age set to 15
print('After setting values changed to',L1.getdata)

print('-'*30)

L2 = License('Chris',17)
print('Before setting',L2.getdata)

L2.getdata=18 #age set to 18
print('After setting values changed to',L2.getdata)

 

Output:


Before setting ('Tom', 25)
Sorry Tom, You are ineligible for License.
After setting values changed to ('Tom', 15)
------------------------------
Before setting ('Chris', 17)
Congrats Chris,You are eligible for License
After setting values changed to ('Chris', 18)

In this program, we have added the setter method which validates the age. By checking whether the age is below 18 or not. If the age is below 18 the class will print the message that the person is not eligible for occupying license and if it is above 18 then the person is eligible for a license.

The statement self._age = age sets whatever the updated age given by the client to the private attribute thereby providing the data hiding feature. In our output, you can observe initially the age of Tom was 25 and later the age was set to 15 by the client. The same reflects in our private attributes also. 

So now our class will automatically update the derived attributes whenever the changes are made to base attributes and vice versa.

The deleter method

Another method associated with @property is the deleter method which is used to delete the attributes from the memory.

 Let’s implement our deleter method to our getdata. Like the setter method, the special syntax to implement deleter is @{methodname}.setter which should be mentioned before the deleter method. The name of the deleter method should be the same as the method that applies to @decorator

Here we are planning to delete the unwanted data from the memory. In our case, we are deleting the data of persons who are ineligible for License. Now to our previous example, we have added the deleter method as illustrated in the following example


class License:
    
    def __init__(self,name,age):
        self.name=name
        self.__age=age

    @property 
    def getdata(self):
        return self.name,self.__age

    @getdata.setter
    def getdata(self,age):
        if age < 18:
            print('Sorry {}, You are ineligible for License.'.format(self.name))
            self.__age=age
        else:
            print('Congrats {},You are eligible for License'.format(self.name))
            self.__age=age
    
    @getdata.deleter
    def getdata(self):
        del self.name
        self.__age=None
             

L1 = License('Tom',25)
L2 = License('Chris',35)

print('Deleting....')
print(L1.getdata)
del L1.getdata

print(L2.name)
print(L1.age)
 

Here we have defined our deleter method by deleting the name attribute with del keyword and the other way to delete the attribute is by assigning the attribute age with None.

Using the del keyword we have deleted one of our instances, L1 and to confirm the deletion we have attempted to print the age in that instance. The following output will clear all the doubts.


Deleting....
('Tom', 25)
Chris
Traceback (most recent call last):
  File "C:\Users\TP-E540\Desktop\PY PRO\atpro_ex.py", line 188, in 
    print(L1.age)
AttributeError: 'License' object has no attribute 'age'

The Property Function

In python, we have the property () function which can be used instead of the @ property decorator. The property() function is a built-in function that returns a property attribute in front of the getter, setter, and deleter. 

Syntax
The syntax of property() is


property(fget=None, fset=None, fdel=None, doc=None)
 

Where,

  • fget(): is the function  used to get the value of the attribute
  • fset(): is the function used to set the value to the attribute
  • fdel(): is the function used to delete the value of the attribute
  • doc: is the string used for documentation of the attribute

These four functional arguments are optional. So that we can create a property object simply as property() also.

A simple example illustration is given below :


class License:
    def __init__(self, name,age):
        self._name = name
        self._age = age

    def get_name(self):
        print('Getting name and age')
        return self._name ,self._age

    def set_name(self, val):
        print('Setting name to ', val)
        self._name = val

    def del_name(self):
        print('Deleting name and age')
        self._name = None
        self._age = None

    # Set property to use get_name, set_name
    # and del_name methods
    name = property(get_name, set_name, del_name, 'Data property')

L = License('Tom',25)
print(L.name)
L.name = 'Jerry'
del L.name

 

Output:


Getting name and age
('Tom', 25)
Setting name to  Jerry
Deleting name and age