@property — Computed Attributes

Expose methods as attributes, add validation, and keep a clean public API without breaking callers.

Coming from other languages you might reach for get_price() and set_price(). Python's answer is the @property decorator: it lets a method be accessed like a plain attribute, so callers write obj.price while you keep full control behind the scenes.

The pattern

class Circle:
    def __init__(self, radius):
        self.radius = radius
    @property
    def area(self):                 # accessed as circle.area, no ()
        return 3.14159 * self.radius ** 2

c = Circle(10)
print(c.area)      # 314.159 — looks like an attribute, runs like a method

Validation with a setter

Add a matching @name.setter to guard assignment. This is the key benefit: you can start with a plain attribute, and later wrap it in a property to add validation without changing a single line of calling code.

Read-only by design

A property with a getter but no setter is read-only — assigning to it raises AttributeError. That is exactly how you expose a derived value that should never be set directly.

Example

Example · python
class Temperature:
    def __init__(self, celsius=0.0):
        self.celsius = celsius        # goes through the setter below

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('below absolute zero')
        self._celsius = float(value)

    @property                          # read-only, derived on the fly
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

    @fahrenheit.setter                 # writable too, in the other direction
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5 / 9

t = Temperature(25)
print('25C in F :', t.fahrenheit)      # 77.0
t.fahrenheit = 212                     # set via the other unit
print('after set:', t.celsius, 'C')    # 100.0 C

try:
    t.celsius = -300                   # validation kicks in
except ValueError as e:
    print('rejected :', e)

# Output:
# 25C in F : 77.0
# after set: 100.0 C
# rejected : below absolute zero

When to use it

  • A User model exposes 'password' as a write-only property that automatically hashes on assignment.
  • A lazy-loading class computes a 'data' property on first access and caches the result for subsequent calls.
  • A geometric class exposes 'diameter' and 'radius' as paired properties that stay in sync.

More examples

Getter and setter with validation

Uses a setter to validate that a percentage stays within 0-100 before storing it.

Example · python
class Percentage:
    @property
    def value(self): return self._value

    @value.setter
    def value(self, v):
        if not 0 <= v <= 100:
            raise ValueError(f'{v} is not 0-100')
        self._value = v

p = Percentage()
p.value = 75
print(p.value)   # 75

Deleter method

Implements a @data.deleter to allow 'del obj.data' to clear a cached value.

Example · python
class CachedData:
    def __init__(self): self._cache = None

    @property
    def data(self):
        if self._cache is None:
            self._cache = 'expensive result'
        return self._cache

    @data.deleter
    def data(self):
        self._cache = None
        print('Cache cleared')

cd = CachedData()
print(cd.data)   # expensive result
del cd.data      # Cache cleared

Paired computed properties

Exposes both radius and diameter as properties that derive from the same internal value.

Example · python
class Circle:
    def __init__(self, radius): self._r = radius

    @property
    def radius(self): return self._r

    @property
    def diameter(self): return self._r * 2

    @diameter.setter
    def diameter(self, d): self._r = d / 2

c = Circle(5)
print(c.diameter)   # 10
c.diameter = 14
print(c.radius)     # 7.0

Discussion

  • Be the first to comment on this lesson.