Syllabus Lesson 52 of 239 · Object-Oriented Python
Object-Oriented Python

Computed Attributes With @property

Sometimes a value should look like an attribute but actually be calculated on the fly, or be checked before it is allowed to change. The @property decorator turns a method into something you read with no parentheses, like a normal attribute.

A read-only computed value

class Rectangle:
    def __init__(self, w, h):
        self.w = w
        self.h = h

    @property
    def area(self):
        return self.w * self.h

r = Rectangle(3, 4)
print(r.area)   # 12, note: no parentheses

Because area is a property, you write r.area, not r.area(). It recalculates every time, so it stays correct even if w or h change.

Validating a value with a setter

You can also guard writes. Define the property, then a matching @<name>.setter. By convention the real data is stored in a private-looking attribute with a leading underscore.

class Person:
    def __init__(self, age):
        self.age = age   # this goes through the setter below

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("age cannot be negative")
        self._age = value

p = Person(30)
print(p.age)   # 30
p.age = 31     # ok
# p.age = -5   # would raise ValueError

From the outside it still looks like a plain attribute, but every read and write runs your code.

Your turn

Define a class Circle whose __init__ takes radius and assigns it to self.radius (which goes through a setter). Add a radius property and a radius setter that raises ValueError when given a negative number, storing the value in self._radius. Add a read-only diameter property that returns radius * 2.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output