Newby Coder header banner

Python Property

Python @property

Property allows to define getter and setters for attributes, which are called implicitly when the attribute is accessed or modified (without requiring a function call from the user/client)

By using property, attributes of a class can be modified while being backward compatible

Use-case

Consider following class being implemented to store user data


class User:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
        self.full_name = self.first_name + ' ' + self.last_name

Consider the use-case when the last_name of a person is changed

>>># create new object
>>> user = User('Ferbina', 'Miller', 22)
>>> user.full_name
Ferbina Miller
>>> user.age
22
>>> user.last_name = 'Darwin'
>>> user.last_name
Darwin
>>> user.full_name
Ferbina Miller

Since, in the above example, full_name is only set initially, it does not get changed when user changes last_name

To mitigate this, full_name can be changed into a function which returns the full name based on first_name and last_name

class User():
    def __init__(self, first_name, last_name):
        self.first = first_name
        self.last = last_name

    def full_name(self):
        return self.first + ' '+ self.last

But this can break code which uses the initial class since code using full_name attribute now have to call full_name() method

Anyone depending on it can be told "this is the way things are now", or @property decorator can be used to allow backward compatibility

@property decorator

A method can be converted to a property by adding a @property decorator before the method’s definition

This makes the method seem & function like an attribute

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    # Adding @property provides the updated full_name, which gets called when full_name is accessed like an attribute
    @property
    def full_name(self):
        return self.first_name + ' '+ self.last_name

user = User('Ferbina', 'Miller')
print(user.__dict__)

user.last_name = 'Darwin'
print(user.__dict__) 

Output

{'first_name': 'Ferbina', 'last_name': 'Miller'}
{'first_name': 'Ferbina', 'last_name': 'Darwin'}

This enables the method full_name() to be accessible as attribute full_name

Python property() method

In Python, property() is a built-in function that creates and returns a property object

The syntax of this function is

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

where, fget is the function to get value of the attribute, fset to set value, fdel to delete the attribute and doc is a string (like a comment)

These function arguments are optional, and can be set later by the methods, getter(), setter(), and deleter() to specify fget, fsetand fdel

Functions setter() and deleter() can also be assigned as decorator

Since the above example doesn't user setter method, attempting to set full_name raises AttributeError

>>>user.full_name = 'Derpina Darwin'
Traceback (most recent call last):
  File "property2.py", line 17, in <module>
    user.full_name = 'Derpina Darwin'
AttributeError: can't set attribute

Python @property setter method

@<property-name>.setter decorator can be used to assign setter for a property, where <property-name> is the name of property (full_name above)

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return self.first_name + ' ' + self.last_name

    @full_name.setter
    def full_name(self, name):
        self.first_name, self.last_name = name.split(" ")

p1 = User("Ferbina", "Miller")
print(p1.__dict__)
p1.full_name = "Ferbina Darwin"
print(p1.__dict__)

Output

{'first_name': 'Ferbina', 'last_name': 'Miller'}
{'first_name': 'Ferbina', 'last_name': 'Darwin'}

Python @property deleter method

@<property-name>.deleter decorator can be used to assign deleter method for a property

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return self.first_name + ' ' + self.last_name

    @full_name.setter
    def full_name(self, name):
        self.first_name, self.last_name = name.split(" ")

    @full_name.deleter
    def full_name(self):
        self.first_name = None
        self.last_name = None


p1 = User("Ferbina", "Miller")
print(p1.__dict__)
del p1.full_name
print(p1.__dict__)

Output

{'first_name': 'Ferbina', 'last_name': 'Miller'}
{'first_name': None, 'last_name': None}