← Back to Python Course | Chapter 7: OOP | Lesson 10 of 11

Python Property Decorators

A property looks like a plain variable but secretly runs code when you use it, like a door with a guard who checks you in. It lets you control how a value is read or changed.
Syntax
python
class ClassName:
    @property
    def attribute(self):
        return self._attribute

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

What is @property?

@property turns a method into something callers access without parentheses, like an ordinary attribute -- def full_name(self): return f'{self.first} {self.last}' becomes obj.full_name instead of obj.full_name() -- which is ideal for read-only computed values.

Example: What is @property?

python
class Person:
    def __init__(self, first, last):
        self.first = first
        self.last = last
    @property
    def full_name(self):  # accessed without parentheses, like an attribute
        return f"{self.first} {self.last}"

p = Person("Alex", "Doe")
print(p.full_name)

The Property Setter

Adding a matching @name.setter method lets that same attribute accept assignment (obj.balance = 100) while running your own validation code first, such as rejecting a negative balance before it's ever stored -- something plain attribute assignment can't intercept.

Example: The Property Setter

python
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")  # validation on assignment
        self._balance = value

a = Account(100)
a.balance = 200  # runs through the setter
print(a.balance)

The Property Deleter

A @name.deleter method runs custom cleanup logic when someone writes 'del obj.balance' -- useful for properties backed by external resources (like a cached file handle) that need explicit teardown rather than just vanishing.

Example: The Property Deleter

python
class Account:
    def __init__(self, balance):
        self._balance = balance
    @property
    def balance(self):
        return self._balance
    @balance.deleter
    def balance(self):  # runs custom logic on del a.balance
        print("Cleaning up balance")
        del self._balance

a = Account(100)
del a.balance  # triggers the deleter above

Calculated Properties

Because a property's getter is just a method under the hood, it can compute its return value fresh every time it's accessed -- a .full_name property can always reflect the current first and last name rather than going stale if those change independently.

Example: Calculated Properties

python
class Person:
    def __init__(self, first, last):
        self.first = first
        self.last = last
    @property
    def full_name(self):  # recomputed every time it's accessed
        return f"{self.first} {self.last}"

p = Person("Alex", "Doe")
p.first = "Sam"
print(p.full_name)  # reflects the updated first name

Refactoring Legacy Code

Properties let you convert a plain public attribute into a validated one later without breaking any existing code that reads or writes obj.balance -- callers keep using the same syntax while your class quietly adds validation or computation behind the scenes.

Example: Refactoring Legacy Code

python
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("Invalid balance")
        self._balance = value

a = Account(100)
a.balance = 50  # still looks like plain attribute access
print(a.balance)
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting that a property is accessed without parentheses, so obj.area() raises TypeError where obj.area is correct.
  2. Defining the setter with a different name than the property, which replaces the property instead of adding a setter.
  3. Storing the value under the same name as the property, as in self.age = value inside the setter, which causes infinite recursion; use self._age.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.