Python has many features, and it's easy to get overwhelmed while learning. Today we will focus on learning just one of those features: the
We will explain what properties are, why you might want to use them, and where they fit within Python's class structure.
When learning to program there is often a steep learning curve after you learn the basics. There is a big gap between tutorials and real-world projects! The
@property decorator is one of those features that you seldom see in tutorials but that can be very useful at times.
What are properties? What are methods?
Too often terms like
method are thrown around when discussing programming languages. It's useful to slow down to define what each term actually means in Python.
An attribute is a variable that is specific to a class object. If you are unfamiliar with what an "object" is, check out this article for a quick review. For example, if you have a class called
Human, an attribute would be any variable that exists on each object of that class. You create attributes by using the
self keyword. "Attribute" is just the name we give to variables that belong to a class. Here's an example, using the
class Human: def __init__(self, age): self.age = age
self.age refers to the attribute that is now on the objects of the
Human class. We can set up many attributes, depending on what we need the class objects to have. For example, we could set
self.eye_color as attributes of objects of the
A method is a function that belongs to a class. For example, in the class called
Human, a method would be any function that exists inside of that class. Here's an example, using our
class Human: def __init__(self, age): self.age = age def get_age(self): return self.age
get_age function is called "a method on the class Human". Important: Methods always receive the argument
self as their first parameter. This is because methods are always connected to a class. In this case,
get_age is what's called an "instance method", and will always receive the object that it is being called on as the first argument. To learn more about each type of method, check out this article.
A property is a special type of attribute. It is an attribute that has two methods assigned to it: a "getter", and a "setter". Let's look at some code to explain further:
class Human: def __init__(self, age): self.age = age def get_age(self): return self.age def set_age(self, new_age): self.age = new_age age = property(get_age, set_age)
As you can see, we are using much of the same code. We included a new "setter" function called
set_age, and then we identified the
age variable as a property of the class, with the getter of
get_age and the setter of
set_age. That is what the
property() function is doing: it takes a getter for the first parameter, a setter for the second parameter, and creates a property variable on the class, which it stores in the name that you assign.
Properties on classes are useful because they allow us to do some dynamic processing on the attributes when we get them or set them. For example, if your class had the attribute
age, but you wanted to make sure that the age value was always given in months, you could ensure that by making the
age attribute a property. Check out this code to see what I mean:
class Human: def __init__(self, age): self.age = age def get_age_in_months(self): return self.age * 12 def set_age(self, new_age): self.age = new_age age = property(get_age_in_months, set_age)
To further streamline the process of creating properties, we will use the
@property decorator. If you haven't ever used/seen decorators, follow our mini-course to get up to speed quickly.
@property decorator works as a shortcut to using the
property() function that we used earlier. On top of that, it makes the code a little bit cleaner. In addition, the
@property decorator executes first upon class initialization, so there's no chance to accidentally use the attribute before it becomes a property. Let's dive in by checking out this code:
class Human: def __init__(self, age): self.age = age @property def age(self): return self._age @property def months(self): return self._age * 12 @age.setter def age(self, new_age): self._age = new_age
Here we use decorators on our getter and setter functions, which we have now renamed to be the name of the attribute. Let's go through each one specifically.
The getter method,
age, is what gets the
@property decorator applied to it. We can use this to format and change what the getter outputs every time the property is referenced.
age is a function, we still need to store the value of the human's age somewhere. That's where the
_age attribute comes into play.
When an object of class
Human is initialized, the
__init__ method will run. This sets the
age attribute on the
self object. Because we wrote a custom setter for the
age property, the custom setter will run.
In the custom setter, we take the incoming
age value and set the
_age attribute on the
self object. At this point, using the setter, we could have more control over how the
_age attribute is set.
__init__ method finishes running, the object will have:
_ageattribute which contains the value passed in when the object was created.
- When accessed calls the
- When changed uses the
- When accessed calls the
monthsproperty that can be accessed but whose value cannot be changed (since there's no setter for it).
To show what I mean, consider the following code:
jeff = Human(26) print('I am ') print(jeff.age) print(' years old.')
What do you think this will print?
This will output the following:
I am 26 years old.
Decorating the getter function with the
@property decorator allows us to change how the property will be served to anyone that calls on it. For example, I can use the code to return the amount of months that the age equates to by creating another property,
@property def months(self): return self._age * 12
Now, anytime that the variable
jeff.months is called, it will return:
Let's dive into the setter. Now that we have defined a property using the
@property decorator, we can write a setter for it using
@[property_name].setter. Remember to replace
property_name with the actual property name inside your class.Let's look at how we did this in the
class Human: def __init__(self, age): self.age = age @property def age(self): return self._age @age.setter def age(self, new_age): self._age = new_age
The setter on the
age property is decorated with the
@age.setter decorator, which allows us to modify how this property is allowed to be set. For example, we could restrict someone's age to be only whole numbers by doing this:
@age.setter def age(self, new_age): if not isinstance(new_age, int): raise TypeError("Age must be an integer.") self._age = new_age
Now, if we try to run the following code:
print(jeff.age) jeff.age = 44.5 print(jeff.age) jeff.age = 50 print(jeff.age)
Here's what the output will be:
26 Traceback (most recent call last): File "main.py", line 3, in <module> TypeError: Age must be an integer.
So you can see that our custom setter function will prevent anything but integers from being set onto the
age property. Because we raised a TypeError, the program will stop running whenever we try to set a non-integer value on the age property. The error message is shown together with a traceback that tells us where the error occurred.
@property decorator in a nutshell! In all, it is a very powerful way to gain more control over how the attributes are set and formatted inside of your classes. By using custom getters, you can modify how you present the attributes. By using custom setters, you can control how each property is allowed to be set.
If you want to learn more about Python, consider enrolling in our Complete Python Course which takes you from beginner all the way to advanced (including OOP, web development, async development, and much more!). We have a 30-day money-back guarantee, so you really have nothing to lose by giving it a try. We'd love to have you!
Photo by Safar Safarov on Unsplash