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.
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.
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?
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.
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 )
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:
@{methodname}.setter
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.
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, inprint(L1.age) AttributeError: 'License' object has no attribute 'age'
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,
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