Skip to content Skip to sidebar Skip to footer

Python Resettable Instance Method Memoization Decorator

I'm attempting to build a decorator for an instance method of a class that will memoize the result. (This has been done a million times before) However, I'd like the option of bein

Solution 1:

Rather than trying to work out the mechanics of your implementation, I've taken the memoized decorator class from PythonDecoratorLibrary, and have modified it to add reset. Below is the result; the trick I've used is to add a callable reset attribute to the decorated function itself.

classmemoized2(object):
       """Decorator that caches a function's return value each time it is called.
       If called later with the same arguments, the cached value is returned, and
       not re-evaluated.
       """def__init__(self, func):
          self.func = func
          self.cache = {}
       def__call__(self, *args):
          try:
             return self.cache[args]
          except KeyError:
             value = self.func(*args)
             self.cache[args] = value
             return value
          except TypeError:
             # uncachable -- for instance, passing a list as an argument.# Better to not cache than to blow up entirely.return self.func(*args)
       def__repr__(self):
          """Return the function's docstring."""return self.func.__doc__
       def__get__(self, obj, objtype):
          """Support instance methods."""
          fn = functools.partial(self.__call__, obj)
          fn.reset = self._reset
          return fn
       def_reset(self):
          self.cache = {}


    classmy_class:
        @memoized2defmy_func(self, val):
            print"in my_func"
            time.sleep(2)
            return val


    c = my_class()

    print"should take time"print c.my_func(55)
    printprint"should be instant"print c.my_func(55)
    print

    c.my_func.reset()

    print"should take time"print c.my_func(55)

Solution 2:

Building upon the answer to the original question given by @aix I have created a class that I think could improve it. The main feature is that the cached values are stored as a property of the instance whose method is being decorated, hence it is very easy to reset them.

classmemoize(object):
  def__init__(self, func):
    #print "Init"
    self.func = func

  def__call__(self, *args):
    #print "Call"ifnot self.func in self.cache:
        self.cache[self.func] = {}
    try:
        return self.cache[self.func][args]
    except KeyError:
        value = self.func(*args)
        self.cache[self.func][args] = value
        return value
    except TypeError:
        # uncachable -- for instance, passing a list as an argument.# Better to not cache than to blow up entirely.return self.func(*args)

  def__repr__(self):
    """Return the function's docstring."""return self.func.__doc__

  def__get__(self, obj, objtype):
    """Support instance methods."""#print "Get", obj, objtype
    fn = functools.partial(self.__call__, obj)
    try:
        self.cache = obj.cache
    except:
        obj.cache = {}
        self.cache = obj.cache
    #print self.cachereturn fn

As an example of usage:

classMyClass(object):
    def__init__(self,data):
        self.data = data

    defupdate(self,data):
        self.data = data
        self.cache = {}

    @memoizedeffunc1(self,x):
        print"Computing func1"return"I am func1 of %s. Data is %s. x is %s\n" % (self, self.data, x)

    @memoizedeffunc2(self,x):
        print"Computing func2"return"I am func2 of %s. Data is %s. x is %s\n" % (self, self.data, x)

    deffunc3(self,x):
        print"Computing func3"return"I am func3 of %s. Data is %s. x is %s\n" % (self, self.data, x)

mc1 = MyClass("data1")
mc2 = MyClass("data2")
mc3 = MyClass("data3")

print mc1.func1(1) 
print mc1.func1(1) 
print mc1.func2(1) 
print mc1.func2(1) 
print mc1.func3(1) 
print mc1.func3(1) 

print mc2.func1(1) 
print mc2.func1(1) 
print mc2.func2(1) 
print mc2.func2(1) 
print mc2.func3(1) 
print mc2.func3(1) 

print"Update mc1\n"
mc1.update("data1new")

print mc1.func1(1) 
print mc1.func2(1) 
print mc1.func3(1) 
print mc2.func1(1) 
print mc2.func2(1) 
print mc2.func3(1) 

gets as output:

Computing func1
I am func1 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1I am func1 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1

Computing func2
I am func2 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1I am func2 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100470fd0>. Data is data1. x is 1

Computing func1
I am func1 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1I am func1 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Computing func2
I am func2 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1I am func2 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Update mc1

Computing func1
I am func1 of <__main__.MyClassobject at 0x100470fd0>. Data is data1new. x is 1

Computing func2
I am func2 of <__main__.MyClassobject at 0x100470fd0>. Data is data1new. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100470fd0>. Data is data1new. x is 1I am func1 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1I am func2 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Computing func3
I am func3 of <__main__.MyClassobject at 0x100476050>. Data is data2. x is 1

Solution 3:

Well, I would like to point out two performance issues in your code. This is not an answer to your question, but I can't make it a comment. Thanks to @delnan for pointing out that has_key is deprecated. Instead of:

try:
        return self.cache[key]
    except KeyError:
        self.cache[key] = self.func(*args, **kwargs)
        return self.cache[key]
    except TypeError:
        # uncacheable, so just return calculated value without cachingreturn self.func(*args, **kwargs)

I would make it this way:

resultDone = False
result = Nonetry:
  if key in self.cache: return self.cache[key]
  else:
    result = self.func(*args, **kwargs)
    resultDone = True
    self.cache[key] = result
except TypeError: # unhashable keypassif resultDone: return result
else: return self.func(*args, **kwargs)

This avoids: a) try/except KeyError; b) calling cache[key] on return; c) calling the function once more on unhashable keys.

Post a Comment for "Python Resettable Instance Method Memoization Decorator"