Memoizing decorator
Daishi Harada
daishi at gmail.com
Wed Dec 7 20:37:02 EST 2005
Hi,
Sorry about the previous dup; I'm posting through
Google Groups which seems to have burped.
Anyways, I've "improved" things (or at least
got things passing more tests). I now bind
the cache to each object instance (and class
for classmethods).
At least one issue still remains, mostly due
to the fact that I still don't really grok how
descriptors are supposed to work.
I managed to get the
@memoize
@classmethod
case to not break by "delegating" to the
classmethod's __get__ from my memoize's
in order to obtain the "correct" underlying
method/function. It seems, however, that
the classmethod implementation doesn't
do the same delegation, so now the flip
case of
@classmethod
@memoize
is having issues where AFAICT there's no
way for the memoize to know what class
it's bound to because classmethod doesn't
invoke it as a decorator. Therefore in that
configuration the memoize is still using
the cache bound to the underlying function
instead of the class object as would be
natural.
Any thoughts/comments would still be
appreciated.
Thanks,
Daishi
---
# All my debugging prints still remain ...
import types
import sys
class DescriptorMemoize(object):
def __init__(self, fn):
print '__init__', self, fn
self.fn = fn
self._cache = {}
def __call__(self, *args):
# Regular invocation case
print '__call__', self, args
return self._callmain(self.fn, self._cache, args)
def _callmain(self, fn, cache, args):
# The basic logic here is the same as in the
# wiki decorator library.
print '_callmain', fn, cache, args
try:
return cache[args]
except KeyError:
value = fn(*args)
cache[args] = value
return value
except TypeError:
return fn(*args)
def __get__(self, inst, owner):
# Method invocation case
print '__get__', self, inst, owner
# Get the right underlying method
try:
fn = self.fn.__get__(inst, owner)
print 'got bound'
except AttributeError:
fn = self.fn
print 'using default fn'
# Get the right cache
# (Generally get the instance's cache)
if (inst is None or
isinstance(self.fn, classmethod)):
print 'using owner cache', owner
obj = owner
else:
print 'using inst cache', inst
obj = inst
try:
# Bypass regular attribute lookup
maincache = obj.__dict__['_memoizecache']
print 'found maincache in obj', obj
except KeyError:
maincache = {}
obj._memoizecache = maincache
print 'created new maincache on', obj
cache = maincache.setdefault(self.fn, {})
print 'maincache+id , cache', maincache, id(maincache), cache
def call(*args):
print 'call', args
return self._callmain(fn, cache, args)
return call
def mk_memoize_test_class(memoize_fn):
class A(object):
n_insts = 0
def __init__(self, n):
self.n = n
A.n_insts += 1
# De-decorate so we can test things easier
def meth(self, x):
print '(eval-meth %s %s)' % (str(self), str(x))
return x+self.n
mem_meth = memoize_fn(meth)
# The following will memoize the wrong answer
# unless all instances are created first.
def clsmeth(cls, x):
print '(eval-clsmeth %s %s)' % (str(cls), str(x))
return x+cls.n_insts
cls_clsmeth = classmethod(clsmeth)
memcls_clsmeth = memoize_fn(cls_clsmeth)
clsmem_clsmeth = classmethod(memoize_fn(clsmeth))
return A
def test_memoize(memoize_fn):
def printsep():
print '-'*30
sys.stdout.flush()
@memoize_fn
def fn(x):
print '(eval %s)' % str(x)
return x
printsep()
print 'bare fn'
print fn(1)
print fn(1)
printsep()
A = mk_memoize_test_class(memoize_fn)
def test_class(a):
for f in ('meth', 'mem_meth'):
print f
m = getattr(a, f)
try:
print m(1)
print m(1)
except Exception, e:
print 'XXX', e
printsep()
for (label, o) in (('inst', a), ('cls', A)):
for f in ('cls_clsmeth', 'memcls_clsmeth',
'clsmem_clsmeth'):
print label
print f
m = getattr(o, f)
try:
print m(1)
print m(1)
except Exception, e:
print 'XXX', e
printsep()
a1 = A(1)
a2 = A(10)
printsep()
print 'Testing a1'
printsep()
test_class(a1)
printsep()
print 'Testing a2'
printsep()
test_class(a2)
if __name__ == '__main__':
test_memoize(DescriptorMemoize)
More information about the Python-list
mailing list