There's nothing particularly inefficient or complicated about how you implemented your caching; that's essentially what needs to happen. It isn't very general, however.
You can implement some sort of more generalized caching strategy, using decorators if you like, for convenience. One possible approach might be:
class Memoizer(object):
def __init__(self):
self._cache = dict()
def memoize_unordered(self, f):
def wrapper(s, *args, **kwargs):
key = (s, f, frozenset(args), frozenset(kwargs.iteritems()))
if key not in self._cache:
print 'calculating', args, kwargs
self._cache[key] = f(s, *args, **kwargs)
return self._cache[key]
return wrapper
def memoize_ordered(self, f):
def wrapper(s, *args, **kwargs):
key = (s, f, tuple(args), frozenset(kwargs.iteritems()))
if key not in self._cache:
print 'calculating', args, kwargs
self._cache[key] = f(s, *args, **kwargs)
return self._cache[key]
return wrapper
memoizer = Memoizer()
class Foo(object):
@memoizer.memoize_unordered
def foo(self, a, b):
return self._calculate(a, b)
def _calculate(self, a, b):
return frozenset([a,b])
foo = Foo()
results = [foo.foo(*a) for a in [(1, 5), (1, 5), (5, 1), (9, 12), (12, 9)]]
for result in results:
print 'RESULT', result
printing:
calculating (1, 5) {}
calculating (9, 12) {}
RESULT frozenset([1, 5])
RESULT frozenset([1, 5])
RESULT frozenset([1, 5])
RESULT frozenset([9, 12])
RESULT frozenset([9, 12])
The downside of course, to implementing caching outside of your object, is that the cache data doesn't get deleted when your object goes away, unless you take some care to make this happen.