A class may need to show a value that changes with its other data, and callers may still read it with dot notation. Python’s property decorator lets a class expose a method as an attribute that can compute a value when read.
I’ll show how to define a computed property and add a setter to validate assignments.
TL;DR
A Python property turns a class method into an attribute-style interface, so the method can compute a value or control reads, assignments and deletion without changing how callers use dot notation.
- Use a getter to calculate or return the value.
- Add a setter when assignment needs validation.
- Keep stored data under a separate backing name.
What Is the Python Property Decorator?
A property lets a class expose a value through an attribute name while running a getter when that value is read.
The built-in property() creates a descriptor, the object Python consults during attribute lookup. The decorator form names the getter, and callers read the result without parentheses.
Unlike a decorator that returns a wrapped function, this decorator replaces the class attribute with a property object. That is why it belongs on a method defined in a class.
The property() constructor accepts accessor functions and an optional documentation string. The matching accessor decorators provide a readable alternative when defining those methods in a class.
This is useful when a class needs validation or a derived value while retaining its existing public attribute name.
Properties are descriptors, but you do not need to implement the descriptor protocol to use @property.
How to Use a Python Property Decorator
The example computes a label from a product name and price. A setter then checks each new price before storing it.
Step 1: Define a computed property
A getter reads the instance’s current values and returns a label without storing a second copy. Changing the price therefore changes the next result automatically.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
@property
def label(self):
return f"{self.name}: ${self.price:.2f}"
Reading item.label calls the getter with that Product instance and returns its result as an attribute value.
Step 2: Add a setter for controlled assignment
A setter handles assignment that needs validation, and its accepted value must go under a separate backing name to avoid recursively invoking that setter.
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("price must be non-negative")
self._price = value
During initialization, assigning the public price invokes the setter. After validation, the value goes into backing storage, rather than invoking the setter again.
Step 3: Read and update the property
Save the complete class and test valid and invalid assignments. The run below also reads the computed label after the update, so the output confirms that it reflects the current price.
The captured output is:
Notebook: $4.50
Notebook: $5.00
ValueError price must be non-negative
The first label uses the initial price, and the second uses the updated value. The invalid assignment raises an exception before replacing _price, so bad data does not enter the object through this interface.
When Should You Use a Property?
Use a property for a short computation or access rule. A plain attribute suits stored data, while a method makes substantial work or required arguments explicit.
The table separates these choices by the work an access needs to perform.
| Case | Prefer | Reason |
|---|---|---|
| Plain stored value | Attribute | No extra access logic is needed. |
| Quick derived value | Property | Read it like an attribute and calculate from current state. |
| Validation on assignment | Property setter | Reject invalid values at the boundary. |
| Long operation or arguments | Method | The call makes work and parameters explicit. |
For example, a rectangle’s area can be calculated from its width and height. Since a getter runs on each access, use an explicit cache when calculation cost makes repeated work unsuitable.
Setters are useful for enforcing an invariant, a condition that must remain true for the object. Raise an exception for rejected input rather than printing a warning and leaving callers to guess whether the assignment worked. If no custom write behavior is needed, a public attribute is simpler than a getter and setter that only return and store values.
A leading underscore, as in _price, is a Python convention that marks an implementation detail. It does not prevent direct access, so use the property as the supported interface.
A property with no setter rejects writes with AttributeError. Define a deleter only if the class has a meaningful deleted state.
If the deleter removes the backing value, later reads fail unless the getter handles that state.
When a class holds data without managed access rules, a Python dataclass can reduce repetitive class code. For class and instance basics, see Python classes and objects.
For the exact accessor behavior, consult the Python documentation for property() and the descriptor guide.
Common Property Errors and Edge Cases
Choose an explicit deleted state before adding deletion support. Otherwise, leave deletion unavailable and keep the backing value intact.
These errors have different symptoms, so the fix depends on which accessor is involved.
- A missing setter makes assignment raise AttributeError. Define a setter if callers should change the value.
- Writing through the public name inside its setter repeats the call. Store the accepted value under a backing name.
- Removing a backing value can make a later read fail. Define how the getter handles that state or disallow deletion.
Conclusion: Keep Attribute Syntax, Add Behavior
Keep the public attribute name stable, and add a property when a concrete computation or validation rule calls for it.
FAQ
These answers cover the common choices behind Python properties.
What does the property decorator do in Python?
It turns a class method into a managed attribute getter, accessed without parentheses. Related setter and deleter methods can control assignment and deletion.
When should I use a Python property?
Use one for a small computed value or controlled attribute access. Keep a plain attribute when no extra behavior is needed, and use a method for substantial work or operations that take arguments.
How do I make a property writable?
Define a getter with @property, then define a method with the same name and decorate it with @property_name.setter. Store the assigned value under a separate backing name to avoid recursion.

