Method 1 (splitting keys and values)
This is based on the idea that dictionaries are just zipped keys and values.
With this idea, we can make something like a dictionary to store keys (function arguments) and values (returned values from the function).
Not sure how slow it will be since it uses list.index. Maybe zipping would be faster?
class memoize:
def __init__(self, func):
self.func = func
self.known_keys = []
self.known_values = []
def __call__(self, *args, **kwargs):
key = (args, kwargs)
if key in self.known_keys:
i = self.known_keys.index(key)
return self.known_values[i]
else:
value = self.func(*args, **kwargs)
self.known_keys.append(key)
self.known_values.append(value)
return value
It works!:
>>> @memoize
... def whatever(unhashable):
... print(*unhashable) # Just to know when called for this example
... return 12345
...
>>> whatever([1, 2, 3, 4])
1 2 3 4
12345
>>> whatever([1, 2, 3, 4])
12345
>>> whatever({"a": "b", "c": "d"})
a c
12345
>>> whatever({"a": "b", "c": "d"})
12345
Method 2 (fake hashes)
class memoize:
def __init__(self, func):
self.func = func
self.known = {}
def __call__(self, *args, **kwargs):
key = give_fake_hash((args, kwargs))
try:
return self.known[key]
except KeyError:
value = self.func(*args, **kwargs)
self.known[key] = value
return value
def give_fake_hash(obj):
cls = type(obj)
name = "Hashable" + cls.__name__
def fake_hash(self):
return hash(repr(self))
t = type(name, (cls, ), {"__hash__": fake_hash})
return t(obj)
Method 2.5 (working for dicts)
import operator
class memoize:
def __init__(self, func):
self.func = func
self.known = {}
def __call__(self, *args, **kwargs):
key = give_fake_hash((args, kwargs))
try:
return self.known[key]
except KeyError:
value = self.func(*args, **kwargs)
self.known[key] = value
return value
def fake_hash(self):
return hash(repr(self))
class HashableTuple(tuple):
__hash__ = fake_hash
class RereprDict(dict):
def __repr__(self):
try:
self._cached_repr
except AttributeError:
self._cached_repr = repr(sorted(self.items(), key=operator.itemgetter(0)))
finally:
return self._cached_repr
__hash__ = fake_hash
def fix_args(args):
for elem in args:
if isinstance(elem, dict):
elem = RereprDict(elem)
yield elem
def give_fake_hash(tup):
args, kwargs = tup
args = tuple(fix_args(args))
kwargs = RereprDict(kwargs)
return HashableTuple((args, kwargs))