Back to Blog
Python

Python Computed Property: Using @property Correctly

python computed property: Learn how to implement computed properties in Python using @property, including getters, setters, caching, and performance considerations.

pythonproperty-decoratoroopcachingperformance
Illustration of a Python computed property showing a getter method transforming an attribute.

python computed property requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In Python, a computed property is an attribute whose value is derived from other data at runtime. The @property decorator turns a method into a read-only attribute, and it can be extended with setter and deleter methods. This pattern keeps logic in one place and avoids duplicating validation or transformation code.

The Property Decorator and Its Role in Computed Attributes

The @property decorator is part of Python's built-in property class. When applied to a method, it makes that method callable as an attribute without parentheses. This is useful when you want to expose a value that is computed on demand, rather than stored explicitly. For example, a Rectangle class might have width and height attributes, and an area computed property that returns width * height. The key benefit is that area always reflects the current dimensions, even if they change after the object is created.

A computed property is not just a convenience; it also encapsulates the calculation logic. If the formula changes, you update it in one place. This is a direct application of the principle of encapsulation.

Basic Computed Property: Read-Only Attribute

The simplest form of a computed property is a read-only attribute. You define a method and decorate it with @property. The method takes self and returns the computed value. Here is a minimal example:

class Circle: def __init__(self, radius): self.radius = radius @property def diameter(self): return self.radius * 2

In this code, diameter is not stored; it is computed from radius each time you access it. If you change radius, diameter updates automatically. This is the core behavior of a computed property: the value is derived, not stored.

Because there is no setter, assigning to diameter raises an AttributeError. This is intentional; it signals that the attribute is read-only. If you need to allow assignment, you must provide a setter.

Adding Setter and Deleter Behavior

To make a computed property writable, you use the @property decorator in combination with @<property>.setter. The setter method receives the value being assigned and can validate or transform it before storing it in an underlying attribute. The getter and setter must share the same method name. Here is an example:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 @fahrenheit.setter def fahrenheit(self, value): self._celsius = (value - 32) * 5 / 9

Here, celsius and fahrenheit are computed properties that share a single underlying attribute _celsius. The setters enforce a physical constraint. This pattern is common when you need to expose multiple representations of the same data while keeping the internal state consistent.

You can also define a deleter with @<property>.deleter. It is called when del obj.property is executed. This is rarely needed, but it can be used to reset the underlying state or to make the property non-existent after deletion.

When to Use a Computed Property vs. a Regular Method

The decision between a computed property and a regular method depends on how the value is intended to be used. A computed property is appropriate when the value behaves like an attribute: it is cheap to compute, does not take arguments, and does not have side effects. For example, area or full_name are good candidates. A method is better when the operation requires arguments, performs significant work, or returns a new object that should not be mistaken for a simple attribute.

Consider a Report class that needs to generate a PDF. That operation is expensive and takes configuration options, so it should be a method like generate_pdf(format='A4'). On the other hand, page_count might be a computed property if it is derived from internal data quickly. The key is to keep the interface intuitive: if calling it with no arguments and getting a value feels like reading an attribute, use a property; if not, use a method.

Caching Computed Properties for Repeated Access

A computed property recalculates its value every time it is accessed. If the underlying data does not change often and the calculation is expensive, this can be wasteful. Python's functools module provides the cached_property decorator, which computes the value once and stores it in the instance's __dict__. Subsequent accesses return the cached value without recomputation. This is a significant performance improvement for properties that are read frequently and depend on immutable data.

from functools import cached_property class DataProcessor: def __init__(self, raw_data): self.raw_data = raw_data @cached_property def processed(self): # Simulate an expensive transformation return sum(self.raw_data) / len(self.raw_data)

Here, processed is computed only on the first access. If raw_data is never modified after initialization, this is safe. However, if you later change raw_data, the cached value becomes stale. You can handle this by deleting the cached value from the instance's __dict__ or by using a regular property with manual caching. cached_property is available in Python 3.8 and later. For older versions, you can implement a simple caching mechanism with a private attribute and a check.

Performance and Runtime Cost of Computed Properties

Every access to a computed property involves a method call. This is slightly slower than direct attribute access, but the difference is usually negligible unless the property is accessed in a tight loop. The real cost is the computation itself. If the calculation is heavy, caching is the appropriate optimization. If the property is accessed millions of times and the computation is trivial, the overhead of the method call might still be measurable, but it is rarely the bottleneck.

Another consideration is that computed properties can hide side effects. If a getter performs I/O or modifies state, it violates the principle of least surprise. Keep getters pure: they should only read existing state and return a derived value. This makes the property predictable and safe to call repeatedly. If you need to perform work that has side effects, use a method instead.

Common Pitfalls and Edge Cases

One common mistake is using a computed property to wrap an attribute that is already directly accessible. This adds indirection without benefit. For example, a property that simply returns self._x is unnecessary unless you need to enforce validation or a specific interface.

Another pitfall is naming conflicts. If you have a property name and also an underlying attribute _name, be careful not to accidentally use name as the storage variable. The getter and setter must use the same method name, and the underlying attribute should have a different name, typically with a leading underscore.

Inheritance can also cause issues. If a subclass overrides the getter, it must also override the setter if the parent had one. Otherwise, the setter from the parent will be lost. This is because the property object is recreated when you override any part of it. To retain the setter, you need to explicitly reapply it in the subclass.

Alternatives: getattr, getattribute, and Descriptors

Computed properties are built on the descriptor protocol. The property class is itself a descriptor. If you need more control, you can implement a custom descriptor class with __get__, __set__, and __delete__ methods. This is useful when you want to reuse the same computed logic across multiple classes or when you need to manage access at a lower level.

The __getattr__ method is another way to compute attributes dynamically. It is called only when normal attribute lookup fails. This can be used to implement computed properties, but it is less explicit and can hide bugs because it intercepts all missing attributes. __getattribute__ is called for every attribute access, which gives maximum control but also introduces significant overhead and complexity. For most cases, @property is the right tool because it is explicit, readable, and easy to maintain.

When you need a computed property that is also settable and you want to share the logic, a custom descriptor is a better choice. For example, you could create a ValidatedField descriptor that validates input in multiple classes. The property decorator is sufficient for a single class, but descriptors scale better when the same behavior is needed in many places.

python computed property: Practical Usage and Code Examples | RYUSLOG DEV