Skip to content

Commit

Permalink
Merge pull request #143 from AguaClara/caching
Browse files Browse the repository at this point in the history
implement a primitive caching layer for simple objects. To properly c…
  • Loading branch information
fletchapin authored Jan 9, 2019
2 parents 4d1e797 + 44ea145 commit 32e2643
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
54 changes: 54 additions & 0 deletions aguaclara/core/cache.py
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()))
62 changes: 62 additions & 0 deletions tests/core/test_cache.py
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

0 comments on commit 32e2643

Please sign in to comment.