Object-Oriented ProgrammingIntermediate6 min46 / 66

Properties

Use @property to expose methods like attributes — computed values and validated setters, without changing how callers use your object.

Sometimes an attribute should be computed on the fly, or a value should be validated before it's stored. Python's @property lets a method be accessed like a plain attribute — no parentheses — so you can add logic later without breaking callers.

a computed, read-only property
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2

c = Circle(10)
print(c.area)   # accessed like an attribute, no ()
c.radius = 20
print(c.area)   # recomputed automatically

#Validated setters

guard how a value is set
class Account:
    def __init__(self, balance):
        self._balance = balance

    @property
    def balance(self):
        return self._balance

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

a = Account(100)
a.balance = 50      # goes through the setter
print(a.balance)    # 50
Tip

Start simple, upgrade later

Begin with a plain attribute (self.radius = radius). If you later need validation or computation, turn it into a @property — callers still write obj.radius, so nothing else changes. This is why Python doesn't need Java-style getters/setters up front.

Quick check

How do you access a value defined with @property?

Key takeaways

  • `@property` turns a method into a read-only attribute accessed without ().
  • Add a matching `@<name>.setter` to validate or transform values on assignment.
  • Store the backing value in a `_name` attribute by convention.
  • You can convert a plain attribute to a property later without changing calling code.
Practice challenges
Test yourself · earn XP
0/3
Predict the output#1

What does this print?

predict-output
class Box:
    def __init__(self, w, h):
        self.w, self.h = w, h
    @property
    def area(self):
        return self.w * self.h

b = Box(3, 4)
print(b.area)
Fix the bug#2

Calling `c.area()` fails with 'float object is not callable'. Why?

fix-bug
class C:
    def __init__(self, r): self.r = r
    @property
    def area(self): return 3.14 * self.r ** 2

c = C(2)
print(c.area())
Fill in the blank#3

Complete the decorator that validates the value when `name` is assigned.

    @name.
    def name(self, value):
        self._name = value.strip()
Your turn
Practice exercise

Give a Temperature class (storing self.celsius) a read-only @property called fahrenheit that returns celsius * 9/5 + 32.

Try it live — edit the code and hit Run to execute real Python:

solution.py · editable