Skip to content

How to Calculate Properties Dynamically of Python Object

Learn how to create computed properties in Python using @property, @prop.setter for validation, and @cached_property for performance optimization in object-oriented programming.

In Python, object attributes are usually stored directly in the instance's dictionary (self.variable). However, there are scenarios where an attribute's value depends on other data and should be calculated on the fly whenever it is accessed. Instead of writing a method like get_area(), Python provides the @property decorator to create computed properties.

This guide explains how to define calculated properties, validate data using setters, and cache expensive computations for performance.

Understanding Computed Properties

A computed property acts like a variable but functions like a method. When you access it, code runs behind the scenes to calculate the result.

Why use them?

  • Clean Syntax: Access data as obj.area instead of obj.calculate_area().
  • Data Consistency: The value is always up-to-date because it is recalculated every time it is accessed.
  • Encapsulation: You can change the internal logic without breaking external code that uses the attribute.

Method 1: The @property Decorator (Basic Calculation)

To create a calculated property, define a method and decorate it with @property.

Example: Geometry Calculation

In this example, area is not stored in memory; it is derived from width and height.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        """Calculates area dynamically based on current dimensions."""
        print("Calculating area...")
        return self.width * self.height

rect = Rectangle(5, 10)

print(f"Area: {rect.area}")

rect.width = 10
print(f"New Area: {rect.area}")

Output:

Calculating area...
Area: 50
Calculating area...
New Area: 100

Method 2: Validation with Setters

Computed properties are also excellent for validation. You can define a "setter" method that runs logic before assigning a value to an underlying private attribute.

Example: Temperature Conversion

Here, setting the fahrenheit property automatically updates the internal _celsius storage.

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def fahrenheit(self):
        return (self._celsius * 9/5) + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        if value < -459.67:
            raise ValueError("Temperature below absolute zero is impossible.")
        print(f"Setting temperature to {value}°F")
        self._celsius = (value - 32) * 5/9

temp = Temperature(25)
print(f"Initial F: {temp.fahrenheit}")

temp.fahrenheit = 212
print(f"Internal Celsius: {temp._celsius}")

try:
    temp.fahrenheit = -500
except ValueError as e:
    print(f"Error: {e}")

Output:

Initial F: 77.0
Setting temperature to 212°F
Internal Celsius: 100.0
Error: Temperature below absolute zero is impossible.

Method 3: Caching Expensive Calculations

If a property requires a heavy computation (e.g., database lookup, complex math), recalculating it on every access is inefficient. You can cache the result using functools.cached_property (Python 3.8+).

This calculates the value once and stores it. The value persists until the object is destroyed, so use this only for attributes that do not change.

from functools import cached_property
import time

class DataSet:
    def __init__(self, data):
        self._data = data

    @cached_property
    def statistics(self):
        print("Performing heavy statistical calculation...")
        time.sleep(1) # Simulate delay
        return {
            "sum": sum(self._data),
            "max": max(self._data),
            "min": min(self._data)
        }

data = DataSet([1, 5, 8, 2, 9])

print("First Access:")
print(data.statistics)

print("\nSecond Access:")
print(data.statistics)

Output:

First Access:
Performing heavy statistical calculation...
{'sum': 25, 'max': 9, 'min': 1}

Second Access:
{'sum': 25, 'max': 9, 'min': 1}

Conclusion

To manage object properties effectively in Python:

  1. Use @property for attributes that depend on other state variables (e.g., area depending on width).
  2. Use @prop.setter to validate input or update internal state when a property is assigned.
  3. Use @cached_property for expensive calculations that only need to run once per object instance.
Navigation