@property

Turns a method into an attribute: the value is computed on read, and an optional setter controls writes.

On this page

Example

#
Python
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)
Resultsaved result, you can check it
3
4
property 'level' of 'Student' object has no setter

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

#
Syntax
class Name:
    @property
    def attribute(self):
        return self._attribute

    @attribute.setter
    def attribute(self, value):
        self._attribute = value

Property 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

#
A setter with validation
Python
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)
Resultsaved result, you can check it
65
Points cannot be negative
65

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.

Computed once: cached_property
Python
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)
Resultsaved result, you can check it
Computing total duration...
60
60

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). Writing self.points = value inside the setter would call that same setter forever.
  • Do not create properties preemptively. Start with a plain attribute and turn it into a @property once you actually need validation or a computed value.

Related entries

#

Spotted a mistake or missing an example? Write to us.