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
Hashes for cached_property_with_invalidation-0.0.2.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | c0af0f1109ad198e6895972e67fdd847de5393e797be340cb5bc334a10d08e9d |
|
MD5 | 116460bde136304cf9dddcf7f1b1c5f6 |
|
BLAKE2b-256 | 8d67849a975f6ea523db688870d46281de68923760399ddcbc7db1a26eefc120 |
Hashes for cached_property_with_invalidation-0.0.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e51652b0b7bf5a766e6f3fa1a8e5df522052d1fcdd74bf22c25b54b21def830e |
|
MD5 | 8925f9a30771e674dd114bc5ff183e26 |
|
BLAKE2b-256 | fef6b2f29a3bf238ccea8516a25979d8fa67f72adb0bb6be9a39f9b7a32176fc |