Cached Properties¶
Concept¶
1. What Is Caching?¶
A cached property computes its value once on first access, stores the result inside the instance (usually in __dict__), and on future accesses, just returns the cached value — skipping recomputation.
2. When to Use¶
Use cached properties when:
- Computation is expensive (database queries, heavy calculations)
- Result doesn't change after first computation
- Want to trade memory for speed
- Need lazy evaluation
3. Built-in vs Custom¶
- Python ≥ 3.8:
@functools.cached_property - Custom: Build your own using descriptors
Custom Implementation¶
1. Descriptor Class¶
class CachedProperty:
def __init__(self, func):
self.func = func
self.__doc__ = func.__doc__
self.name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
if self.name not in instance.__dict__:
print(f"Computing and caching: {self.name}")
instance.__dict__[self.name] = self.func(instance)
else:
print(f"Using cached value: {self.name}")
return instance.__dict__[self.name]
2. Using Custom Descriptor¶
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@CachedProperty
def area(self):
print("Calculating area...")
return math.pi * self.radius ** 2
@CachedProperty
def perimeter(self):
print("Calculating perimeter...")
return 2 * math.pi * self.radius
3. Behavior Example¶
c = Circle(10)
print(c.area) # → Calculates and caches
# Output: Computing and caching: area
# Calculating area...
# 314.159...
print(c.area) # → Uses cached value
# Output: Using cached value: area
# 314.159...
How It Works¶
1. Initialization¶
When you decorate:
@CachedProperty
def area(self):
...
You are doing at class definition time:
Circle.area = CachedProperty(area)
The CachedProperty.__init__ stores:
- self.func = area → original method
- self.__doc__ = area.__doc__ → docstring
- self.name = 'area' → attribute name
2. First Access¶
When you do c.area, Python calls:
Circle.area.__get__(c, Circle)
Inside __get__:
- Check if
instance is None(class access) → return descriptor - Check if
'area'ininstance.__dict__→ not yet - Compute:
instance.__dict__['area'] = self.func(instance) - Return cached value
3. Subsequent Access¶
When you do c.area again:
- Check if
instance is None→ no - Check if
'area'ininstance.__dict__→ yes, found! - Skip computation, return cached value directly
Built-in Version¶
1. Using functools¶
Python 3.8+ provides built-in cached property:
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print("Computing area...")
return math.pi * self.radius ** 2
2. Behavior¶
c = Circle(10)
print(c.area) # Computing area... 314.159...
print(c.area) # 314.159... (no recomputation)
3. Clearing Cache¶
To clear cached value:
del c.area # Remove from instance.__dict__
print(c.area) # Recomputes
Practical Examples¶
1. Database Query¶
class User:
def __init__(self, user_id):
self.user_id = user_id
@cached_property
def profile(self):
print(f"Fetching profile for user {self.user_id}...")
# Expensive database query
return db.query(f"SELECT * FROM users WHERE id={self.user_id}")
2. File Reading¶
class ConfigFile:
def __init__(self, path):
self.path = path
@cached_property
def data(self):
print(f"Reading {self.path}...")
with open(self.path) as f:
return f.read()
3. Complex Computation¶
class Matrix:
def __init__(self, data):
self.data = data
@cached_property
def determinant(self):
print("Computing determinant...")
# Expensive calculation
return self._compute_determinant()
Comparison Table¶
1. Property vs Cached Property¶
| Feature | @property |
@cached_property |
|---|---|---|
| Computation | Every access | Once on first access |
| Storage | Not stored | Stored in __dict__ |
| Speed | Slower | Faster after first |
| Memory | Less | More |
| Use case | Dynamic values | Static expensive values |
2. When to Choose¶
Use @property when:
- Value changes over time
- Computation is cheap
- Need fresh value each time
Use @cached_property when:
- Value is constant after creation
- Computation is expensive
- Multiple accesses expected