@property
Overview
The `@property` decorator transforms a class method so it can be accessed like a normal attribute, evaluating its logic on-the-fly. If you have an `User` object, you can compute `user.full_name` dynamically rather than storing it. You can also define `.setter` and `.deleter` methods under the same property name to intercept assignment (`user.full_name = 'Alice Smith'`) or deletion. The mental model: variables are usually direct labels to objects, but a property is a 'computed label' that secretly runs a function whenever you read, write, or delete it. Analogy: a property is like an automatic sliding door—it looks like a regular wall (an attribute), but approaching it triggers a hidden motor (a function call).
It allows you to safely change internal implementation without breaking the external API. You can start with a simple attribute, and later add validation or computation without forcing everyone to rewrite `obj.attr` to `obj.get_attr()`.
Where used: SQLAlchemy models, Pydantic computed fields, Django models
Why learn this
- You can add validation to class attributes without changing the public API.
- It makes objects cleaner to use, avoiding Java-style get_foo() and set_foo() sprawl.
Code walkthrough
class User:
def __init__(self, age):
self._age = age
@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
u = User(20)
u.age = 25
print(u.age)
Focus: u.age = 25
Aha moment
class BankAccount:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, amount):
self._balance = amount
acc = BankAccount(100)
acc.balance += 50
print(acc.balance)
Prediction: What does this print?
Common guess: It raises an error because you can't use += on a property.
The `+=` operator automatically expands to `acc.balance = acc.balance + 50`, effectively calling both the getter to read the current balance and the setter to write the new balance.
Common mistakes
- Infinite Recursion in Setters: If a setter for `@foo.setter` modifies `self.foo = value`, it triggers the setter again! You must modify a private underlying attribute like `self._foo = value`.
- Missing the @property on Getters: You cannot define `@foo.setter` unless `foo` is already defined as a `@property` method first.
Glossary
- evaluating
- Running a piece of code or calculation to determine its final result or value. Example: `result = 2 + 2`.
- sprawl
- When code becomes messy, spread out, and difficult to manage because too many similar or repetitive methods are used. Example: `obj.get_name()`.
Recall questions
- Why would you use `@property` instead of a regular method like `get_full_name()`?
- What happens if you try to assign a value to a property that only has a getter defined?
- What is the correct mental model for how a property functions when accessed?
Understanding checks
What happens when you run this code?
It raises an AttributeError.
The `env` property only has a getter defined. Attempting to assign to a property without a corresponding `@env.setter` raises an `AttributeError`.
Why would you choose to use a `@property` instead of leaving `age` as a simple public integer attribute on a `User` class?
To allow future validation or computation without breaking the external API.
Using a property lets external code keep using `user.age`, but allows you to add a hidden function to intercept assignments (e.g., preventing negative values) without forcing everyone to change to `user.set_age(...)`.
Practice tasks
Implement a Read-Only Computed Property
Modify the starter code for the `Rectangle` class to add an `@property` called `area` that computes and returns `self.width * self.height` on-the-fly. Do not provide a setter.
Challenge
Validating Password Length
Create a `UserAccount` class that initializes with a `username` and a `password`. Use a property for `password` to ensure it is always stored in `self._password`. The setter should raise a `ValueError` with the message 'Password too short' if the new password is less than 8 characters long. The constructor should also validate the initial password using this setter.
Continue learning
Previous: Decorators