Decorator to create cached_property that can be invalidated when invalidation variable is updated
Project description
cached_property_with_invalidation
Decorator to create cached_property that can be invalidated when invalidation variable is updated
Installing
Install:
pip install cached_property_with_invalidation
Usage
Example usage comparing:
-
cached_property_with_invalidation
-
cached_property
-
property
This example demonstrates a common use case in which we have a slow computation that we want to cache temporarily, but we need to invalidate the cache when the class state updates. If we simply use property
, no caching will be done, which hurts performance. If we simply use cached_property
, caching will be done, but we will get incorrect values when the class state updates. In contrast, using cached_property_with_invalidation
allows us to correctly compute the right values when the class state updates, but caches the value when it has not been updated.
This is a very common use-case in physics-based simulation, where we have a simulation physics state that is updated on each simulation step, and we have expensive computations on that physics state we want to cache.
from cached_property_with_invalidation import (
cached_property_with_invalidation,
)
import time
try:
from functools import cached_property
except ImportError:
from functools import lru_cache
def cached_property(func):
@property
@lru_cache()
def wrapped_method(self):
return func(self)
return wrapped_method
SLOW_FUNCTION_TIME_MIN_SECONDS = 0.1
CACHE_ACCESS_TIME_MAX_SECONDS = 0.01
INVALIDATION_VARIABLE_NAME = "counter"
class ExampleClass:
def __init__(self):
self.counter = 0
self.internal_state = [i for i in range(10)]
def update_state(self):
self.counter += 1
self.internal_state = [i + 1 for i in self.internal_state]
def slow_double_internal_state(self):
time.sleep(SLOW_FUNCTION_TIME_MIN_SECONDS)
return [i * 2 for i in self.internal_state]
@cached_property_with_invalidation(INVALIDATION_VARIABLE_NAME)
def slow_double_internal_state_with_cache_and_invalidation(self):
return self.slow_double_internal_state()
@cached_property
def slow_double_internal_state_with_cache_no_invalidation(self):
return self.slow_double_internal_state()
@property
def slow_double_internal_state_no_cache(self):
return self.slow_double_internal_state()
def test_with_cache_and_invalidation():
# Correct behavior and fast
example_class = ExampleClass()
t0 = time.time()
output0 = example_class.slow_double_internal_state_with_cache_and_invalidation
t1 = time.time()
cached_output0 = (
example_class.slow_double_internal_state_with_cache_and_invalidation
)
t2 = time.time()
example_class.update_state()
t3 = time.time()
output1 = example_class.slow_double_internal_state_with_cache_and_invalidation
t4 = time.time()
cached_output1 = (
example_class.slow_double_internal_state_with_cache_and_invalidation
)
t5 = time.time()
compute_output0_time = t1 - t0
compute_cached_output0_time = t2 - t1
compute_output1_time = t4 - t3
compute_cached_output1_time = t5 - t4
assert output0 == cached_output0
assert output0 != output1
assert output1 == cached_output1
assert compute_output0_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_cached_output0_time < CACHE_ACCESS_TIME_MAX_SECONDS
assert compute_output1_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_cached_output1_time < CACHE_ACCESS_TIME_MAX_SECONDS
def test_with_cache_no_invalidation():
# Fast but incorrect behavior
example_class = ExampleClass()
t0 = time.time()
output0 = example_class.slow_double_internal_state_with_cache_no_invalidation
t1 = time.time()
cached_output0 = example_class.slow_double_internal_state_with_cache_no_invalidation
t2 = time.time()
example_class.update_state()
t3 = time.time()
output1 = example_class.slow_double_internal_state_with_cache_no_invalidation
t4 = time.time()
cached_output1 = example_class.slow_double_internal_state_with_cache_no_invalidation
t5 = time.time()
compute_output0_time = t1 - t0
compute_cached_output0_time = t2 - t1
compute_output1_time = t4 - t3
compute_cached_output1_time = t5 - t4
assert output0 == cached_output0
assert output0 == output1
assert output1 == cached_output1
assert compute_output0_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_cached_output0_time < CACHE_ACCESS_TIME_MAX_SECONDS
assert compute_output1_time < CACHE_ACCESS_TIME_MAX_SECONDS
assert compute_cached_output1_time < CACHE_ACCESS_TIME_MAX_SECONDS
def test_no_cache():
# Correct behavior but slow
example_class = ExampleClass()
t0 = time.time()
output0 = example_class.slow_double_internal_state_no_cache
t1 = time.time()
uncached_output0 = example_class.slow_double_internal_state_no_cache
t2 = time.time()
example_class.update_state()
t3 = time.time()
output1 = example_class.slow_double_internal_state_no_cache
t4 = time.time()
uncached_output1 = example_class.slow_double_internal_state_no_cache
t5 = time.time()
compute_output0_time = t1 - t0
compute_uncached_output0_time = t2 - t1
compute_output1_time = t4 - t3
compute_uncached_output1_time = t5 - t4
assert output0 == uncached_output0
assert output0 != output1
assert output1 == uncached_output1
assert compute_output0_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_uncached_output0_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_output1_time > SLOW_FUNCTION_TIME_MIN_SECONDS
assert compute_uncached_output1_time > SLOW_FUNCTION_TIME_MIN_SECONDS
test_with_cache_and_invalidation()
test_with_cache_no_invalidation()
test_no_cache()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file cached_property_with_invalidation-0.0.2.tar.gz
.
File metadata
- Download URL: cached_property_with_invalidation-0.0.2.tar.gz
- Upload date:
- Size: 4.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.18
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c0af0f1109ad198e6895972e67fdd847de5393e797be340cb5bc334a10d08e9d |
|
MD5 | 116460bde136304cf9dddcf7f1b1c5f6 |
|
BLAKE2b-256 | 8d67849a975f6ea523db688870d46281de68923760399ddcbc7db1a26eefc120 |
File details
Details for the file cached_property_with_invalidation-0.0.2-py3-none-any.whl
.
File metadata
- Download URL: cached_property_with_invalidation-0.0.2-py3-none-any.whl
- Upload date:
- Size: 4.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.18
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e51652b0b7bf5a766e6f3fa1a8e5df522052d1fcdd74bf22c25b54b21def830e |
|
MD5 | 8925f9a30771e674dd114bc5ff183e26 |
|
BLAKE2b-256 | fef6b2f29a3bf238ccea8516a25979d8fa67f72adb0bb6be9a39f9b7a32176fc |