-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #143 from AguaClara/caching
implement a primitive caching layer for simple objects. To properly c…
- Loading branch information
Showing
2 changed files
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# The cache decorator does not support objects nested within datastructures or other objects. It is quite limited | ||
# because the hash function is limited. This could be revisited and we could use a serialization library like Pickle | ||
# if this proved to be an issue (which it most likely will when we get more complex classes) | ||
|
||
import collections | ||
import warnings | ||
|
||
# The cache has key=(function name , parameter serialization) and value= | ||
__ac_cache__ = {} | ||
|
||
|
||
def ac_cache(method): | ||
def _cache(*args, **kw): | ||
global __cache__ | ||
param_list = [args, kw] | ||
params_key = tuple([method.__name__, ac_hash(param_list)]) | ||
try: | ||
value = __ac_cache__[params_key] | ||
except KeyError: | ||
value = method(*args, **kw) | ||
__ac_cache__[params_key] = value | ||
|
||
return value | ||
|
||
# Attempt to hash any object | ||
def ac_hash(hashable_object): | ||
if is_simple_hashable(hashable_object): | ||
a_hash = repr(hashable_object) | ||
elif isinstance(hashable_object, HashableObject): | ||
a_hash = hashable_object.ac_hash() | ||
elif isinstance(hashable_object, collections.Iterable): | ||
a_hash = ac_hash_iterable_into_tuple(hashable_object) | ||
else: | ||
a_hash = repr(hashable_object) | ||
warnings.warn("Using repr() to make a hash of {}. Please consider inheriting HashableObject class as repr " | ||
"will not guarantee replicable hashing and can result in bad cache returns.".format( | ||
repr(hashable_object)), Warning, stacklevel=2) | ||
return a_hash | ||
|
||
def ac_hash_iterable_into_tuple(hashable_object_list): | ||
hash_tuple = () | ||
for hashable_object in hashable_object_list: | ||
hash_tuple += (ac_hash(hashable_object),) | ||
return hash_tuple | ||
|
||
primitive = (int, str, bool, ...) | ||
def is_simple_hashable(thing): | ||
return type(thing) in primitive | ||
|
||
return _cache | ||
|
||
class HashableObject: | ||
def ac_hash(self): | ||
return tuple(sorted(self.__dict__.items())) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from aguaclara.core.cache import ac_cache, HashableObject | ||
|
||
|
||
class ComputedObject(HashableObject): | ||
def __init__(self): | ||
self.a = 2 | ||
self.b = 3 | ||
self.c = 4 | ||
|
||
@property | ||
@ac_cache | ||
def product(self): | ||
increment_n_calls() | ||
return self.a * self.b * self.c | ||
|
||
@property | ||
@ac_cache | ||
def sum(self): | ||
increment_n_calls() | ||
return self.a + self.b + self.c | ||
|
||
@ac_cache | ||
def sum_with_arg(self, my_arg): | ||
increment_n_calls() | ||
return self.sum + my_arg | ||
|
||
@ac_cache | ||
def sum_with_kwarg(self, my_arg=10): | ||
increment_n_calls() | ||
return self.sum + my_arg | ||
|
||
|
||
# Keep track of the total number of calls | ||
side_effect_n_calls = 0 | ||
|
||
|
||
def increment_n_calls(): | ||
global side_effect_n_calls | ||
side_effect_n_calls = side_effect_n_calls +1 | ||
|
||
|
||
def test_ac_cache(): | ||
my_computed_object = ComputedObject() | ||
assert 24 == my_computed_object.product | ||
assert 1 == side_effect_n_calls | ||
assert 9 == my_computed_object.sum | ||
assert 2 == side_effect_n_calls | ||
assert 9 == my_computed_object.sum | ||
assert 2 == side_effect_n_calls | ||
my_computed_object.a=3 | ||
assert 36 == my_computed_object.product | ||
assert 3 == side_effect_n_calls | ||
assert 10 == my_computed_object.sum | ||
assert 4 == side_effect_n_calls | ||
assert 15 == my_computed_object.sum_with_arg(5) | ||
assert 5 == side_effect_n_calls | ||
assert 20 == my_computed_object.sum_with_kwarg() | ||
assert 6 == side_effect_n_calls | ||
assert 20 == my_computed_object.sum_with_kwarg() | ||
assert 6 == side_effect_n_calls | ||
assert 25 == my_computed_object.sum_with_kwarg(my_arg=15) | ||
assert 7 == side_effect_n_calls |