A decorator for caching properties in classes.
npx @tessl/cli install tessl/pypi-cached-property@2.0.00
# Cached Property
1
2
A decorator for caching properties in classes that provides comprehensive property caching functionality for Python classes. The library enables developers to cache expensive property computations and avoid redundant calculations through multiple caching strategies including basic caching, thread-safe caching, time-based expiration with TTL support, and async/await compatibility.
3
4
## Package Information
5
6
- **Package Name**: cached-property
7
- **Language**: Python
8
- **Installation**: `pip install cached-property`
9
- **Python Requirements**: `>=3.8`
10
- **License**: BSD
11
12
## Core Imports
13
14
```python
15
from cached_property import cached_property
16
```
17
18
For thread-safe environments:
19
20
```python
21
from cached_property import threaded_cached_property
22
```
23
24
For time-based cache expiration:
25
26
```python
27
from cached_property import cached_property_with_ttl
28
```
29
30
For both thread safety and TTL:
31
32
```python
33
from cached_property import threaded_cached_property_with_ttl
34
```
35
36
Alternative alias imports:
37
38
```python
39
from cached_property import cached_property_ttl, timed_cached_property
40
from cached_property import threaded_cached_property_ttl, timed_threaded_cached_property
41
```
42
43
## Basic Usage
44
45
```python
46
from cached_property import cached_property
47
48
class DataProcessor:
49
def __init__(self, data):
50
self.data = data
51
52
@cached_property
53
def expensive_computation(self):
54
# This computation only runs once per instance
55
result = sum(x ** 2 for x in self.data)
56
print("Computing...") # This will only print once
57
return result
58
59
# Usage
60
processor = DataProcessor([1, 2, 3, 4, 5])
61
print(processor.expensive_computation) # Computes and caches
62
print(processor.expensive_computation) # Returns cached value
63
64
# Cache invalidation
65
del processor.expensive_computation # or del processor.__dict__['expensive_computation']
66
print(processor.expensive_computation) # Computes again
67
```
68
69
## Capabilities
70
71
### Basic Property Caching
72
73
The fundamental cached property decorator that computes a property value once per instance and then replaces itself with an ordinary attribute. Supports both synchronous and asynchronous property functions.
74
75
```python { .api }
76
class cached_property:
77
"""
78
A property that is only computed once per instance and then replaces itself
79
with an ordinary attribute. Deleting the attribute resets the property.
80
"""
81
82
def __init__(self, func):
83
"""
84
Initialize the cached property decorator.
85
86
Args:
87
func: The function to be cached (can be sync or async)
88
"""
89
90
def __get__(self, obj, cls):
91
"""
92
Descriptor protocol implementation.
93
94
Args:
95
obj: The instance accessing the property
96
cls: The owner class
97
98
Returns:
99
The cached property value or a coroutine wrapper for async functions
100
"""
101
```
102
103
**Usage Example:**
104
105
```python
106
from cached_property import cached_property
107
import asyncio
108
109
class Example:
110
@cached_property
111
def regular_property(self):
112
return "computed once"
113
114
@cached_property
115
async def async_property(self):
116
await asyncio.sleep(0.1) # Simulate async work
117
return "async computed once"
118
119
# Regular usage
120
example = Example()
121
print(example.regular_property) # Computes
122
print(example.regular_property) # Cached
123
124
# Async usage
125
async def main():
126
example = Example()
127
result = await example.async_property # Computes and caches
128
result2 = await example.async_property # Returns cached future
129
130
asyncio.run(main())
131
```
132
133
### Thread-Safe Property Caching
134
135
A thread-safe version of cached_property for environments where multiple threads might concurrently try to access the property. Uses reentrant locking to ensure only one computation occurs even with concurrent access.
136
137
```python { .api }
138
class threaded_cached_property:
139
"""
140
A cached_property version for use in environments where multiple threads
141
might concurrently try to access the property.
142
"""
143
144
def __init__(self, func):
145
"""
146
Initialize the thread-safe cached property decorator.
147
148
Args:
149
func: The function to be cached
150
"""
151
152
def __get__(self, obj, cls):
153
"""
154
Thread-safe descriptor protocol implementation.
155
156
Args:
157
obj: The instance accessing the property
158
cls: The owner class
159
160
Returns:
161
The cached property value
162
"""
163
```
164
165
**Usage Example:**
166
167
```python
168
from cached_property import threaded_cached_property
169
from threading import Thread
170
import time
171
172
class ThreadSafeExample:
173
def __init__(self):
174
self.compute_count = 0
175
176
@threaded_cached_property
177
def thread_safe_property(self):
178
time.sleep(0.1) # Simulate work
179
self.compute_count += 1
180
return f"computed {self.compute_count} times"
181
182
example = ThreadSafeExample()
183
184
# Multiple threads accessing simultaneously
185
threads = []
186
for i in range(10):
187
thread = Thread(target=lambda: print(example.thread_safe_property))
188
threads.append(thread)
189
thread.start()
190
191
for thread in threads:
192
thread.join()
193
194
# Only computed once despite concurrent access
195
assert example.compute_count == 1
196
```
197
198
### Time-Based Cache Expiration (TTL)
199
200
Property caching with time-to-live (TTL) expiration support. The cached value expires after a specified time period, forcing recomputation on next access. Supports manual cache invalidation and setting.
201
202
```python { .api }
203
class cached_property_with_ttl:
204
"""
205
A property that is only computed once per instance and then replaces itself
206
with an ordinary attribute. Setting the ttl to a number expresses how long
207
the property will last before being timed out.
208
"""
209
210
def __init__(self, ttl=None):
211
"""
212
Initialize the TTL cached property decorator.
213
214
Args:
215
ttl (float, optional): Time-to-live in seconds. If None, cache never expires.
216
"""
217
218
def __call__(self, func):
219
"""
220
Allow usage as decorator with parameters.
221
222
Args:
223
func: The function to be cached
224
225
Returns:
226
self
227
"""
228
229
def __get__(self, obj, cls):
230
"""
231
TTL-aware descriptor protocol implementation.
232
233
Args:
234
obj: The instance accessing the property
235
cls: The owner class
236
237
Returns:
238
The cached property value (recomputed if TTL expired)
239
"""
240
241
def __delete__(self, obj):
242
"""
243
Manual cache invalidation.
244
245
Args:
246
obj: The instance to clear cache for
247
"""
248
249
def __set__(self, obj, value):
250
"""
251
Manual cache setting.
252
253
Args:
254
obj: The instance to set cache for
255
value: The value to cache
256
"""
257
```
258
259
**Usage Example:**
260
261
```python
262
from cached_property import cached_property_with_ttl
263
import time
264
import random
265
266
class TTLExample:
267
@cached_property_with_ttl(ttl=2) # Cache expires after 2 seconds
268
def random_value(self):
269
return random.randint(1, 100)
270
271
# Can also be used without TTL (cache never expires)
272
@cached_property_with_ttl
273
def permanent_cache(self):
274
return "never expires"
275
276
example = TTLExample()
277
278
# Initial computation
279
value1 = example.random_value
280
print(f"First: {value1}")
281
282
# Within TTL - returns cached value
283
value2 = example.random_value
284
print(f"Second: {value2}") # Same as value1
285
286
# Wait for TTL expiration
287
time.sleep(3)
288
289
# After TTL - recomputes
290
value3 = example.random_value
291
print(f"Third: {value3}") # Different from value1
292
293
# Manual cache control
294
example.random_value = 999 # Set cache manually
295
print(example.random_value) # Returns 999
296
297
del example.random_value # Clear cache manually
298
print(example.random_value) # Computes new value
299
```
300
301
### Thread-Safe TTL Property Caching
302
303
Combines thread safety with TTL expiration. Uses reentrant locking to ensure thread-safe access while supporting time-based cache expiration. Inherits all TTL functionality with thread safety guarantees.
304
305
```python { .api }
306
class threaded_cached_property_with_ttl(cached_property_with_ttl):
307
"""
308
A cached_property version for use in environments where multiple threads
309
might concurrently try to access the property.
310
"""
311
312
def __init__(self, ttl=None):
313
"""
314
Initialize the thread-safe TTL cached property decorator.
315
316
Args:
317
ttl (float, optional): Time-to-live in seconds. If None, cache never expires.
318
"""
319
320
def __get__(self, obj, cls):
321
"""
322
Thread-safe TTL-aware descriptor protocol implementation.
323
324
Args:
325
obj: The instance accessing the property
326
cls: The owner class
327
328
Returns:
329
The cached property value (recomputed if TTL expired)
330
"""
331
```
332
333
**Usage Example:**
334
335
```python
336
from cached_property import threaded_cached_property_with_ttl
337
from threading import Thread
338
import time
339
340
class ThreadSafeTTLExample:
341
def __init__(self):
342
self.access_count = 0
343
344
@threaded_cached_property_with_ttl(ttl=5) # 5-second TTL
345
def thread_safe_ttl_property(self):
346
self.access_count += 1
347
return f"computed {self.access_count} times at {time.time()}"
348
349
example = ThreadSafeTTLExample()
350
351
# Multiple threads accessing within TTL
352
def access_property():
353
return example.thread_safe_ttl_property
354
355
threads = []
356
for i in range(5):
357
thread = Thread(target=access_property)
358
threads.append(thread)
359
thread.start()
360
361
for thread in threads:
362
thread.join()
363
364
# Only computed once despite concurrent access
365
assert example.access_count == 1
366
```
367
368
## Convenience Aliases
369
370
The library provides convenient aliases for easier usage:
371
372
```python { .api }
373
# Aliases for cached_property_with_ttl
374
cached_property_ttl = cached_property_with_ttl
375
timed_cached_property = cached_property_with_ttl
376
377
# Aliases for threaded_cached_property_with_ttl
378
threaded_cached_property_ttl = threaded_cached_property_with_ttl
379
timed_threaded_cached_property = threaded_cached_property_with_ttl
380
```
381
382
**Usage with Aliases:**
383
384
```python
385
from cached_property import cached_property_ttl, timed_cached_property
386
387
class AliasExample:
388
@cached_property_ttl(ttl=10)
389
def using_ttl_alias(self):
390
return "cached with alias"
391
392
@timed_cached_property(ttl=5)
393
def using_timed_alias(self):
394
return "cached with timed alias"
395
```
396
397
## Cache Management
398
399
### Cache Invalidation
400
401
Remove cached values to force recomputation:
402
403
```python
404
# Method 1: Delete the property directly (works with all cached_property variants)
405
del instance.property_name
406
407
# Method 2: Delete from instance dictionary (works with basic cached_property only)
408
del instance.__dict__['property_name']
409
410
# Method 3: For TTL variants, use the __delete__ method
411
# (This is called automatically when using del instance.property_name)
412
```
413
414
### Cache Inspection
415
416
Access cached values without triggering computation:
417
418
```python
419
class Inspector:
420
@cached_property
421
def my_property(self):
422
return "computed value"
423
424
inspector = Inspector()
425
426
# Check if property is cached
427
if 'my_property' in inspector.__dict__:
428
cached_value = inspector.__dict__['my_property']
429
print(f"Cached: {cached_value}")
430
else:
431
print("Not cached yet")
432
433
# For TTL variants, cached values are stored as (value, timestamp) tuples
434
class TTLInspector:
435
@cached_property_with_ttl(ttl=60)
436
def ttl_property(self):
437
return "ttl value"
438
439
ttl_inspector = TTLInspector()
440
_ = ttl_inspector.ttl_property # Trigger caching
441
442
if 'ttl_property' in ttl_inspector.__dict__:
443
value, timestamp = ttl_inspector.__dict__['ttl_property']
444
print(f"Value: {value}, Cached at: {timestamp}")
445
```
446
447
## Error Handling
448
449
The cached property decorators preserve and cache exceptions:
450
451
```python
452
from cached_property import cached_property
453
454
class ErrorExample:
455
@cached_property
456
def failing_property(self):
457
raise ValueError("This always fails")
458
459
example = ErrorExample()
460
461
try:
462
_ = example.failing_property
463
except ValueError:
464
print("Exception occurred and was cached")
465
466
# Subsequent access returns the same exception
467
try:
468
_ = example.failing_property # Same exception, no recomputation
469
except ValueError:
470
print("Same cached exception")
471
472
# Clear cache to retry
473
del example.failing_property
474
```
475
476
## Integration with Async/Await
477
478
The basic `cached_property` decorator automatically detects and handles coroutine functions:
479
480
```python
481
import asyncio
482
from cached_property import cached_property
483
484
class AsyncExample:
485
@cached_property
486
async def async_data(self):
487
# Simulate async operation
488
await asyncio.sleep(0.1)
489
return "async result"
490
491
async def main():
492
example = AsyncExample()
493
494
# First access - computes and caches a Future
495
result1 = await example.async_data
496
497
# Subsequent access - returns the same Future
498
result2 = await example.async_data
499
500
print(f"Result: {result1}") # "async result"
501
assert result1 == result2
502
503
asyncio.run(main())
504
```
505
506
**Note**: Async support is only available with the basic `cached_property` decorator. The threaded variants are not compatible with asyncio due to thread safety concerns with event loops.