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) -- http://mail.python.org/mailman/listinfo/python-list