The @property decorator simplifies the management of attributes in your Python classes. It allows you to control attribute access, enabling features such as data validation, lazy evaluation, and the creation of backward-compatible APIs without modifying the class’s public interface. By using @property, you can avoid the clutter of getter and setter methods, keeping your code clean and Pythonic.
By the end of this tutorial, you’ll understand that:
- A property in Python is a tool for creating managed attributes in classes.
- The
@propertydecorator allows you to define getter, setter, and deleter methods for attributes. - You use properties when you need controlled access or want to encapsulate logic without changing the API.
- Properties are useful for validating data, computing attributes, and creating read-only or read-write attributes.
- You should avoid
@propertywhen direct access is sufficient or performance is critical.
First, you’ll learn how to use property() as a function through practical examples. These examples will demonstrate how to validate input data, compute attribute values dynamically, log your code, and more. Then you’ll explore the @property decorator, the most common syntax for working with properties. To get the most out of this tutorial, you should know the basics of object-oriented programming, classes, and decorators in Python.
Get Your Code: Click here to download the free sample code that shows you how to use Python’s property() to add managed attributes to your classes.
Take the Quiz: Test your knowledge with our interactive “Python's property(): Add Managed Attributes to Your Classes” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python's property(): Add Managed Attributes to Your ClassesIn this quiz, you'll test your understanding of Python's property(). With this knowledge, you'll be able to create managed attributes in your classes, perform lazy attribute evaluation, provide computed attributes, and more.
Object-Oriented Python Live Course: Want to practice using @property to protect your class invariants in a real project? In Real Python’s Object-Oriented Python live course, you build a complete simulator with an instructor, adding properties, alternative constructors, and other OOP tools only when the design needs them. Join the waitlist to hear about the next dates.
Managing Attributes in Your Classes
When you define a class in an object-oriented programming language, you’ll probably end up with some instance and class attributes. In other words, you’ll end up with variables that are accessible through the instance, class, or even both, depending on the language. Attributes represent and hold the internal state of a given object, which you’ll often need to access and mutate.
Typically, you have at least two ways to access and mutate an attribute. Either you can access and mutate the attribute directly or you can use methods. Methods are functions attached to a given class. They provide the behaviors and actions that an object can perform with its internal data and attributes.
If you expose attributes to the user, then they become part of the class’s public API. This means that your users will access and mutate them directly in their code. The problem comes when you need to change the internal implementation of a given attribute.
Say you’re working on a Circle class and add an attribute called .radius, making it public. You finish coding the class and ship it to your end users. They start using Circle in their code to create a lot of awesome projects and applications. Good job!
Now suppose that you have an important user that comes to you with a new requirement. They don’t want Circle to store the radius any longer. Instead, they want a public .diameter attribute.
At this point, removing .radius to start using .diameter could break the code of some of your other users. You need to manage this situation in a way other than removing .radius.
Programming languages such as Java and C++ encourage you to never expose your attributes to avoid this kind of problem. Instead, you should provide getter and setter methods, also known as accessors and mutators, respectively. These methods offer a way to change the internal implementation of your attributes without changing your public API.
Note: Getter and setter methods are often considered an anti-pattern and a signal of poor object-oriented design. The main argument behind this proposition is that these methods break encapsulation. They allow you to access and mutate the components of your objects from the outside.
These programming languages need getter and setter methods because they don’t have a suitable way to change an attribute’s internal implementation when a given requirement changes. Changing the internal implementation would require an API modification, which can break your end users’ code.
The Getter and Setter Approach in Python
Technically, there’s nothing that stops you from using getter and setter methods in Python. Here’s a quick example that shows how this approach would look:
point_v1.py
class Point:
def __init__(self, x, y):
self._x = x
self._y = y
def get_x(self):
return self._x
def set_x(self, value):
self._x = value
def get_y(self):
return self._y
def set_y(self, value):
self._y = value
In this example, you create a Point class with two non-public attributes ._x and ._y to hold the Cartesian coordinates of the point at hand.
Note: Python doesn’t have the notion of access modifiers, such as private, protected, and public, to restrict access to attributes and methods. In Python, the distinction is between public and non-public class members.
If you want to signal that a given attribute or method is non-public, then you have to use the well-known Python convention of prefixing the name with an underscore (_). That’s the reason behind the naming of the attributes ._x and ._y.
Note that this is just a convention. It doesn’t stop you and other programmers from accessing the attributes using dot notation, as in obj._attr. However, it’s bad practice to violate this convention.
To access and mutate the value of either ._x or ._y, you can use the corresponding getter and setter methods. Go ahead and save the above definition of Point in a Python module and import the class into an interactive session. Then run the following code:
>>> from point_v1 import Point
>>> point = Point(12, 5)
>>> point.get_x()
12
>>> point.get_y()
5
>>> point.set_x(42)
>>> point.get_x()
42
>>> # Non-public attributes are still accessible
>>> point._x
42
>>> point._y
5
With .get_x() and .get_y(), you can access the current values of ._x and ._y. You can use the setter method to store a new value in the corresponding managed attribute. From the two final examples, you can confirm that Python doesn’t restrict access to non-public attributes. Whether or not you access them directly is up to you.
The Pythonic Approach
Even though the example you just saw uses the Python coding style, it isn’t Pythonic. In the example, the getter and setter methods don’t perform any further processing with the values of ._x and ._y, so you could just have plain attributes instead of methods.
You can rewrite Point in a more concise and Pythonic way:
>>> class Point:
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> point = Point(12, 5)
>>> point.x
12
>>> point.y
5
>>> point.x = 42
>>> point.x
42
This code uncovers a fundamental Python principle: exposing attributes to the end user is normal and common. This is cool because you don’t need to clutter your classes with getter and setter methods all the time.
The question is, how do you handle requirement changes that would imply modifying the implementation of attributes without changing your APIs? For example, say that you need to add validation functionality on top of a given attribute. How would you do that if your attribute doesn’t have setter and getter methods where you can put that functionality?
Unlike Java and C++, Python provides handy tools that allow you to change the underlying implementation of your attributes without changing your public API. The most popular approach is to turn your attributes into properties.
Note: Another common approach to providing managed attributes is to use descriptors. In this tutorial, however, you’ll learn about properties only. If you want to dig into descriptors, check out the Python Descriptors: An Introduction tutorial.
Properties represent an intermediate functionality between a plain attribute, or field, and a method. In other words, they allow you to create methods that behave like attributes. With properties, you can change how you compute the target attribute whenever you need to.
For example, you can turn both .x and .y into properties. With this change, you can continue to access them as attributes while having them perform actions when your users access and mutate them.
Note: Properties aren’t exclusive to Python. Languages such as JavaScript, C#, Kotlin, and others also provide tools and techniques for creating properties as class members.
Python properties allow you to expose attributes as part of your classes’ public APIs. If you ever need to change an attribute’s underlying implementation, then you can conveniently turn it into a property at any time. In the following sections, you’ll learn how to create properties in Python.
Getting Started With Python’s property()
Using Python’s property() is the Pythonic way to avoid getter and setter methods in your classes. This built-in function allows you to turn class attributes into properties or managed attributes. Because property() is a built-in function, you can use it without importing anything. Additionally, property() is implemented in C, which ensures optimized performance.
Note: It’s common to refer to property() as a built-in function. However, property is a class with a function-like name. That’s why most Python developers call it a function.
In this tutorial, you’ll follow the common practice of calling property() a function rather than a class. However, in some sections, you’ll see it called a class to facilitate the explanation.
With property(), you can attach implicit getter and setter methods to given class attributes. You can also specify a way to handle attribute deletion and provide an appropriate docstring for your properties.
Here’s the full signature of property():
property([fget=None, fset=None, fdel=None, doc=None])
The first two arguments take function objects that will play the role of getter (fget) and setter (fset) methods. Python automatically calls these function objects when you access or mutate the attribute at hand.
Here’s a summary of what each argument does:
| Argument | Description |
|---|---|
fget |
A function object that returns the value of the managed attribute |
fset |
A function object that allows you to set the value of the managed attribute |
fdel |
A function object that defines how the managed attribute handles deletion |
doc |
A string representing the property’s docstring |
The return value of property() is the managed attribute itself. If you access the managed attribute with something like obj.attr, then Python automatically calls fget(). If you assign a new value to the attribute with something like obj.attr = value, then Python calls fset() using the input value as an argument. Finally, if you run a del obj.attr statement, then Python automatically calls fdel().
Note: The first three arguments to property() take function objects. You can think of a function object as the function name without the calling pair of parentheses.
You can use doc to provide an appropriate docstring for your properties. You and your fellow programmers will be able to read that docstring using Python’s help(). The doc argument is also useful when you’re working with code editors and IDEs that support docstring access.
You can use property() either as a function or decorator to build your properties. In the following two sections, you’ll learn how to use both approaches. However, the decorator approach is more popular in the Python community.
Creating Attributes With property()
You can create a property by calling property() with an appropriate set of arguments and assigning its return value to a class attribute. All the arguments to property() are optional. However, you typically provide at least a getter function.
The following example shows how to create a Circle class with a property that manages its radius:
circle_v1.py
class Circle:
def __init__(self, radius):
self._radius = radius
def _get_radius(self):
print("Get radius")
return self._radius
def _set_radius(self, value):
print("Set radius")
self._radius = value
def _del_radius(self):
print("Delete radius")
del self._radius
radius = property(
fget=_get_radius,
fset=_set_radius,
fdel=_del_radius,
doc="The radius property."
)
In this code snippet, you create Circle. The class initializer, .__init__(), takes radius as an argument and stores it in a non-public attribute called ._radius. Then, you define three non-public methods:
._get_radius()returns the current value of._radius._set_radius()takesvalueas an argument and assigns it to._radius._del_radius()deletes the instance attribute._radius
Once you have these three methods in place, you create a class attribute called .radius to store the property object. To initialize the property, you pass the three methods as arguments to property(). You also pass a suitable docstring for your property.
In this example, you use keyword arguments to improve readability and prevent confusion. That way, you know exactly which method goes into each argument.
To give Circle a try, run the following code in your Python REPL:
>>> from circle_v1 import Circle
>>> circle = Circle(42.0)
>>> circle.radius
Get radius
42.0
>>> circle.radius = 100.0
Set radius
>>> circle.radius
Get radius
100.0
>>> del circle.radius
Delete radius
>>> circle.radius
Get radius
Traceback (most recent call last):
...
AttributeError: 'Circle' object has no attribute '_radius'.
⮑ Did you mean: 'radius'?
>>> help(circle)
Help on Circle in module circle_v1 object:
class Circle(builtins.object)
...
| radius
| The radius property.
The .radius property hides the non-public instance attribute ._radius, which is now your managed attribute in this example. You can access and assign .radius directly. Internally, Python automatically calls ._get_radius() and ._set_radius() when needed. When you execute del circle.radius, Python calls ._del_radius(), which deletes the underlying ._radius.
Besides using regular named functions to provide getter methods in your properties, you can also use lambda functions.
Here’s a version of Circle in which the .radius property uses a lambda function as its getter method:
>>> class Circle:
... def __init__(self, radius):
... self._radius = radius
... radius = property(lambda self: self._radius)
...
>>> circle = Circle(42.0)
>>> circle.radius
42.0
If your getter method’s functionality is limited to returning the current value of the managed attribute, then using a lambda function can be the solution.
Properties are class attributes that manage instance attributes. You can think of a property as a collection of methods bundled together. If you inspect .radius carefully, then you’ll find the raw methods you provided as the fget, fset, and fdel arguments:
>>> Circle.radius.fget
<function Circle._get_radius at 0x7fba7e1d7d30>
>>> Circle.radius.fset
<function Circle._set_radius at 0x7fba7e1d78b0>
>>> Circle.radius.fdel
<function Circle._del_radius at 0x7fba7e1d7040>
>>> dir(Circle.radius)
[..., '__get__', ..., '__set__', ...]
You can access the getter, setter, and deleter methods in a given property through the corresponding .fget, .fset, and .fdel attributes.
Properties are also descriptors. If you use dir() to check the internal members of a given property, then you’ll find .__set__() and .__get__() in the list. These methods provide a default implementation of the descriptor protocol.
Note: If you want to better understand the internal implementation of property as a class, then check out the pure Python Property class described in the documentation.
The default implementation of .__set__(), for example, runs when you don’t provide a custom setter method. This implementation gives you an AttributeError when you try to set the attribute.
Using property() as a Decorator
Decorators are frequently used in Python. They’re typically functions that take another function as an argument and return a new function with added functionality. With a decorator, you can attach pre- and post-processing operations to an existing function.
Note: In Python, you can define decorators using either a function or a class. So, you can have both function-based and class-based decorators.
The decorator syntax consists of placing the name of the decorator function with a leading @ symbol right before the definition of the function you want to decorate: