@property
Turns a method into an attribute: the value is computed on read, and an optional setter controls writes.
Example
#class Student:
def __init__(self, name, points=0):
self.name = name
self.points = points
@property
def level(self):
return self.points // 100 + 1
emma = Student("Emma", 250)
print(emma.level)
emma.points += 100
print(emma.level)
try:
emma.level = 10
except AttributeError as error:
print(error)Definition and usage
#The @property decorator lets you read a method like a plain attribute, without parentheses: student.level instead of student.level(). The value is computed on every read, so it always matches the object’s current state, for example a level computed from points.
A property with no setter is read-only, and trying to assign to it raises AttributeError. To allow a controlled write, add a second method with the same name decorated with @name.setter. The setter can check or transform the new value before storing it in an attribute with an underscore, e.g. self._points.
Properties let you start with a plain attribute and add validation later, without changing the code that uses the class. That is why Python code does not write get_points() and set_points() methods up front, the way Java does. The functools.cached_property decorator caches the result of an expensive computation that does not change.
Syntax
#class Name:
@property
def attribute(self):
return self._attribute
@attribute.setter
def attribute(self, value):
self._attribute = valueProperty elements
#@property
object.x
The getter: the method run on read. Its result becomes the attribute’s value.@x.setter
object.x = v
The setter: the method run on assignment. It receives the new value as a parameter. Without it, the attribute is read-only.@x.deleter
del object.x
The method run when the attribute is deleted. Rarely needed.
More examples
#class Student:
def __init__(self, name, points=0):
self.name = name
self.points = points
@property
def points(self):
return self._points
@points.setter
def points(self, value):
if value < 0:
raise ValueError("Points cannot be negative")
self._points = value
jack = Student("Jack", 40)
jack.points += 25
print(jack.points)
try:
jack.points = -5
except ValueError as error:
print(error)
print(jack.points)The assignment self.points = points in __init__ also goes through the setter, so you cannot even create an object with a negative number of points.
from functools import cached_property
class Course:
def __init__(self, title, lessons):
self.title = title
self.lessons = lessons
@cached_property
def total_minutes(self):
print("Computing total duration...")
return sum(self.lessons)
python = Course("Python", [15, 20, 25])
print(python.total_minutes)
print(python.total_minutes)Good practices
#- A property should run fast and change nothing. Readers of the code assume that reading an attribute is a simple, safe operation.
- Store the value in an attribute with a different name, usually with an underscore (
self._points). Writingself.points = valueinside the setter would call that same setter forever. - Do not create properties preemptively. Start with a plain attribute and turn it into a
@propertyonce you actually need validation or a computed value.
Related entries
#- DecoratorsFunctions that wrap other functions and add behavior to them, such as logging or permission checks. Written as @name.
- classDefines a class: a template for objects that combines data with the functions that act on it.
- selfThe first parameter of a method: the object the method was called on. It gives access to that object’s attributes and other methods.
- @dataclassA decorator that generates __init__, __repr__, and __eq__ for a data-storing class, based on type-annotated fields.
Spotted a mistake or missing an example? Write to us.