Python Property

Summary: in this tutorial, you’ll learn about the Python property class and how to use it to define properties for a class.

Introduction to class properties

The following defines a Person class that has two attributes name and age, and create a new instance of the Person class:

class Person: def __init__(self, name, age): self.name = name self.age = age john = Person('John', 18)Code language: Python (python)

Since age is the instance attribute of the Person class, you can assign it a new value like this:

john.age = 19Code language: Python (python)

The following assignment is also technically valid:

john.age = -1Code language: Python (python)

However, the age is semantically incorrect.

To ensure that the age is not zero or negative, you use the if statement to add a check as follows:

age = -1 if age <= 0: raise ValueError('The age must be positive') else: john.age = ageCode language: Python (python)

And you need to do this every time you want to assign a value to the age attribute. This is repetitive and difficult to maintain.

To avoid this repetition, you can define a pair of methods called getter and setter.

Getter and setter

The getter and setter methods provide an interface for accessing an instance attribute:

  • The getter returns the value of an attribute
  • The setter sets a new value for an attribute

In our example, you can make the age attribute private (by convention) and define a getter and a setter to manipulate the age attribute.

The following shows the new Person class with a getter and setter for the age attribute:

class Person: def __init__(self, name, age): self.name = name self.set_age(age) def set_age(self, age): if age <= 0: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._ageCode language: Python (python)

How it works.

In the Person class, the set_age() is the setter and the get_age() is the getter. By convention the getter and setter have the following name: get_<attribute>() and set_<attribute>().

In the set_age() method, we raise a ValueError if the age is less than or equal to zero. Otherwise, we assign the age argument to the _age attribute:

def set_age(self, age): if age <= 0: raise ValueError('The age must be positive') self._age = ageCode language: Python (python)

The get_age() method returns the value of the _age attribute:

def get_age(self): return self._ageCode language: Python (python)

In the __init__() method, we call the set_age() setter method to initialize the _age attribute:

def __init__(self, name, age): self.name = name self.set_age(age)Code language: Python (python)

The following attempts to assign an invalid value to the age attribute:

john = Person('John', 18) john.set_age(-19)Code language: Python (python)

And Python issued a ValueError as expected.

ValueError: The age must be positiveCode language: Python (python)

This code works just fine. But it has a backward compatibility issue.

Suppose you released the Person class for a while and other developers have been already using it. And now you add the getter and setter, all the code that uses the Person won’t work anymore.

To define a getter and setter method while achieving backward compatibility, you can use the property() class.

The Python property class

The property class returns a property object. The property() class has the following syntax:

property(fget=None, fset=None, fdel=None, doc=None)Code language: Python (python)

The property() has the following parameters:

  • fget is a function to get the value of the attribute, or the getter method.
  • fset is a function to set the value of the attribute, or the setter method.
  • fdel is a function to delete the attribute.
  • doc is a docstring i.e., a comment.

The following uses the property() function to define the age property for the Person class.

class Person: def __init__(self, name, age): self.name = name self.age = age def set_age(self, age): if age <= 0: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._age age = property(fget=get_age, fset=set_age)Code language: Python (python)

In the Person class, we create a new property object by calling the property() and assign the property object to the age attribute. Note that the age is a class attribute, not an instance attribute.

The following shows that the Person.age is a property object:print(Person.age)Code language: Python (python)

Output:

<property object at 0x000001F5F5149180>Code language: Python (python)

The following creates a new instance of the Person class and access the age attribute:

john = Person('John', 18)Code language: Python (python)

The john.__dict__ stores the instance attributes of the john object. The following shows the contents of the john.__dict__ :

print(john.__dict__)Code language: Python (python)

Output:

{'_age': 18, 'name': 'John'}Code language: Python (python)

As you can see clearly from the output, the john.__dict__ doesn’t have the age attribute.

The following assigns a value to the age attribute of the john object:

john.age = 19Code language: Python (python)

In this case, Python looks up the age attribute in the john.__dict__ first. Because Python doesn’t find the age attribute in the john.__dict__, it’ll then find the age attribute in the Person.__dict__.

The Person.__dict__ stores the class attributes of the Person class. The following shows the contents of the Person.__dict__:

pprint(Person.__dict__)Code language: Python (python)

Output:

mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>, '__doc__': None, '__init__': <function Person.__init__ at 0x000002242F5B2670>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Person' objects>, 'age': <property object at 0x000002242EE39180>, 'get_age': <function Person.get_age at 0x000002242F5B2790>, 'set_age': <function Person.set_age at 0x000002242F5B2700>})Code language: Python (python)

Because Python finds the age attribute in the Person.__dict__, it’ll call the age property object.

When you assign a value to the age object:

john.age = 19Code language: Python (python)

Python will call the function assigned to the fset argument, which is the set_age().

Similarly, when you read from the age property object, Python will execute the function assigned to the fget argument, which is the get_age() method.

By using the property() class, we can add a property to a class while maintaining backward compatibility. In practice, you will define the attributes first. Later, you can add the property to the class if needed.

Putting it all together.

from pprint import pprint class Person: def __init__(self, name, age): self.name = name self.age = age def set_age(self, age): if age <= 0: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._age age = property(fget=get_age, fset=set_age) print(Person.age) john = Person('John', 18) pprint(john.__dict__) john.age = 19 pprint(Person.__dict__)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *