runtime
137 / 250
lesson 137 Data Models & Typing · advanced

Properties: computed and validated attributes

A property looks like an attribute to the code that uses it, but runs a method when it is read, and optionally another when it is set. That lets a class compute values on demand and refuse bad assignments.

You'll be able to
  • Turn a computed method into a read-only attribute with @property
  • Add a setter with @name.setter that converts and validates assigned values
  • Run the setter's checks in __init__ too, so construction and later changes obey the same rule
The idea

In the Classes & Objects module a line's total was either stored (and went stale when the quantity changed) or a method (line.total()). A property gives you both: callers write line.total like an attribute, and every read runs a method, so it is always current.

Put @property above a method that takes only self. The method's name becomes the attribute name. There is no setter yet, so assigning to it is an error: a read-only attribute.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · A line total that is always current
class OrderLine:
    def __init__(self, sku, quantity, unit_price):
        self.sku = sku
        self.quantity = quantity
        self.unit_price = unit_price

    @property
    def total(self):
        return round(self.quantity * self.unit_price, 2)

line = OrderLine("SPO-11681", 2, 17.92)
print(line.total)
line.quantity = 3
print(line.total)
example-2.py · Assigning to a read-only property
class OrderLine:
    def __init__(self, sku, quantity, unit_price):
        self.sku = sku
        self.quantity = quantity
        self.unit_price = unit_price

    @property
    def total(self):
        return round(self.quantity * self.unit_price, 2)

line = OrderLine("SPO-11681", 2, 17.92)
print(line.total)
line.total = 30.0

A property can also have a setter: @price.setter above a second method with the same name, taking the new value. It runs on every assignment, so it can convert and validate. The value itself is stored under a different name, by convention with a leading underscore (self._price), which means "internal: use the property".

If __init__ assigns through the property (self.price = price), the setter's checks also run when the object is created, so there is one rule for both.

example-3.py · A price that can't go negative
class Product:
    def __init__(self, sku, price):
        self.sku = sku
        self.price = price

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        value = round(float(value), 2)
        if value < 0:
            raise ValueError(f"{self.sku}: price can't be negative ({value})")
        self._price = value

mat = Product("SPO-11681", "31")
print(mat.price)
mat.price = 27.499
print(mat.price)
for bad in [-5, "free"]:
    try:
        mat.price = bad
    except ValueError as err:
        print("refused:", err)
print(mat.price)
try:
    Product("HOM-22847", -48)
except ValueError as err:
    print("refused:", err)

When to use it: for values derived from other attributes (totals, full names, flags), and for attributes with rules that must hold after every change. Start with plain attributes; turning one into a property later doesn't change any calling code.

Mistakes to avoid: storing the value under the property's own name inside the setter (self.price = value calls the setter again, forever: RecursionError); slow work in a property (callers expect attribute reads to be cheap); and properties with surprising side effects.

Review
  • @property runs a method when an attribute is read, so derived values are always current.
  • @name.setter runs on assignment; store the value under an internal name such as _price.
  • Assign through the property in __init__ so creation and later changes follow the same rule.
  • Next: special methods that make objects print, compare, add up and answer len and in.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Properties: computed and validated attributes — Data Models & Typing · Runtime