Python @property Decorator
Have you ever wondered how to implement Getters, Setters, and Deleters in Python? The property() method and the @property decorator provide elegant solutions to all related problems. In this article, we'll explore why we need Getters, Setters, and Deleters and how to implement them - both via the property() method, and the @property decorator!
A very crucial component of Object Oriented Programming is the concept of Getters, Setters, and Deleters. Python eases the use of these functions with the help of Property Decorators, and in this article, we’ll explore just that!
We’ll cover Getters, Setters, and Deleters, why we need them, and how we can use them to ensure ease of use for you and the developers who use your class in their projects.
Class Without Getters and Setters
The use of python property decorators can be best explained by first defining the problem.
Consider the following code –
We first define a class Person which has attributes firstName, middleName, lastName and fullname. An instance of Person is instantiated, and the first name of the person is changed to something else. Users of this class would expect the attribute fullname to change dynamically, but in this scenario, it remains the same, which makes the class practically unusable.
This code gives the output:
As you can see, the attribute fullname did not get updated when we changed person1’s first name. An easy way of fixing this is by removing the line:
And by adding the function fullname() in the Person class
Now, any time we want to use the person's full name, we’ll have to call the function fullname(), which will return the value as a string.
The problem with this fix is that we’ll have to call the newly created fullname() function as a function instead of calling it as an attribute.
So, print statements will go from this:
If our codebase is small, making this change won’t be a problem, but many issues arise as our application scales to over a few thousand lines of code. People using your class will also have to change all instances of
Which is not practical. If you try to call the attribute directly, that is, without the parentheses, instead of calling the function, it will return the function itself, with its address –
To fix this in the ‘object oriented’ way, languages like Java use Getter functions. The good news is that we can implement Getter functions in Python with property decorators!
Now that you’ve been convinced that this topic is useful, we’ll go back to the basics, and build the concepts from the ground up.
What is property() in Python?
The property() method returns a property attribute from the defined Getter, Setter, and Deleter.
Before we get to Getters, Setters and Deleters, it would be useful to recall the scope of variables in object-oriented programming -
When class attributes and functions are defined in the class, they can be defined as public, private, or protected variables/functions. In languages like C++ and Java, we cannot access private attributes outside the class they are defined in. Protected values of class X can be accessed by classes that inherit from class X.
In python, however, the concept of public, private, and protected attributes does not exist, so as a naming convention, variables that start with an underscore character are considered non-public variables. They can technically still be accessed outside the class, so keep that in mind!
Example of a non-private variable being accessed outside its class (this is to be avoided!)
Getters, Setters, and Deleters
In object-oriented programming, when a class gets created by convention, the attributes are made private. It is a good practice to make them private, so they don’t get changed accidentally when called by objects.
These attributes can then be retrieved, manipulated or deleted by getters, setters, and deleters.
Getters fetch the value that is held in the attribute.
Setters take a value, as a function parameter and then change the private attribute’s value to the new one.
Deleters get called when we’re done using the attribute and we need to free up its memory.
The following example highlights these good practices in python –
In the above code snippet, we first instantiate an instance of class Foo and set the person's name to Mike, which gets printed out using the getter.
Then, the name is changed from Mike to Jason using the class' setter method. The updated name is printed again with the same getter method as before.
Towards the end, the object's deleter is called, after which retrieving the person's name throws an Attribute Error (which shows that the name was deleted successfully).
This was the ‘unofficial’ way of using getters, setters, and deleters. Python makes this whole process elegant with the property() method and the @property decorator!
Declaring a property()
The property() function -
The property() method takes 4 arguments:
- fget – function that acts as a Getter
- fset – function that acts as a Setter
- fdel – function that acts as a Deleter
- doc – String will be parsed into a docstring that explains what the returned property attribute does (like a docstring below a function that explains the function).
All four are set to None by default.
The property() function returns a property object. On printing it, we get -
This property object gets invoked whenever the instantiated object fetches, changes, or deletes the attribute(s) outside the class. For example, instead of calling the Setter function setName(), we can directly do obj.name = "new name".
The following example will make this notion clearer. It implements the same logic from example 3 but with the property() method instead.
Output: - Exactly the same as [Example 3]
Each attribute can have its property attribute. As an exercise, try to add a _fullname attribute and its respective getter, setter, and deleter with the property() function.
If the doc argument is not mentioned, the docstring under the Getter function is used for the entire property attribute.
Using the @property Decorator
A python decorator is a function that accepts another function as an argument.
A decorator's point is to enhance the function passed to it. It returns the modified function, which can then be called normally.
Decorators in python are very powerful and help make classes easier to understand and use. They help add new features to a python object without having to modify the structure of that object.
The @property decorator does the same thing as the property() method but makes the code cleaner and easier to read. The following code implements @property, but does the same as examples 3 and 4.
Output: - Exactly the same as [Example 3]
As you can see, there’s no need to define a property attribute object using property(). The @property tells us exactly what each function does and whether it is a getter, setter, or deleter.
The @property decorator makes sense out of context provided and then applies the appropriate function. For example, the first time it is used, it is used in a print function, so it calls the object getter. The second time name is used when changing the person’s name from Mike to Jason. Although we have used the same call, person.name, it invokes the setter and passes ‘Jason’ as an argument to the setter. That’s pretty neat.
Now, armed with the @property decorator, we’ll wrap this article by solving the problem stated at the very beginning of the article
[Example 1] – the one involving fullname
The following steps will fix the problem using Python’s Property Decorator.
- Remove the fullname attribute from __init__()
- Create the fullname() function the usual way
- Add the line ‘@property’ above the function definition
- Call fullname as if you were calling the object’s attribute
We’ve also defined the setter for fullname for you, so as an exercise try changing the first, middle and last names using the fullname setter.
And voila! That fixes our issue. Now all class users can continue using fullname the way they were using while getting the freedom to dynamically alter the full name by changing the person’s first, middle, or last name.
Now that you've read this, you’ll be able to use property decorators, getters, setters, and deleters easily and make your user's life a lot easier!
In this article we covered -
- How programs without Getters, Setters, and Deleters become difficult to maintain and not scalable.
- The property() function, to which we can pass the getter, setter, and deleter functions as arguments and get the behaviour required.
- Basic object oriented design in Python.
- Getters, Setters, and Deleters through the @property decorator.
All with plenty of use cases and examples through which your next project will be more object oriented and maintainable!
Thank you for taking the time to read this article!