python resettable instance method memoization decorator

前端 未结 3 1977
旧巷少年郎
旧巷少年郎 2020-12-16 02:41

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 optio

相关标签:
3条回答
  • 2020-12-16 02:54

    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.

    class memoize(object):
      def __init__(self, func):
        #print "Init"
        self.func = func
    
      def __call__(self, *args):
        #print "Call"
        if not 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.cache
        return fn
    

    As an example of usage:

    class MyClass(object):
        def __init__(self,data):
            self.data = data
    
        def update(self,data):
            self.data = data
            self.cache = {}
    
        @memoize
        def func1(self,x):
            print "Computing func1"
            return "I am func1 of %s. Data is %s. x is %s\n" % (self, self.data, x)
    
        @memoize
        def func2(self,x):
            print "Computing func2"
            return "I am func2 of %s. Data is %s. x is %s\n" % (self, self.data, x)
    
        def func3(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__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    I am func1 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    Computing func2
    I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1. x is 1
    
    Computing func1
    I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    Computing func2
    I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    Update mc1
    
    Computing func1
    I am func1 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1
    
    Computing func2
    I am func2 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100470fd0>. Data is data1new. x is 1
    
    I am func1 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    I am func2 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    Computing func3
    I am func3 of <__main__.MyClass object at 0x100476050>. Data is data2. x is 1
    
    0 讨论(0)
  • 2020-12-16 02:59

    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.

        class memoized2(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 = {}
    
    
        class my_class:
            @memoized2
            def my_func(self, val):
                print "in my_func"
                time.sleep(2)
                return val
    
    
        c = my_class()
    
        print "should take time"
        print c.my_func(55)
        print
    
        print "should be instant"
        print c.my_func(55)
        print
    
        c.my_func.reset()
    
        print "should take time"
        print c.my_func(55)
    
    0 讨论(0)
  • 2020-12-16 02:59

    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 caching
            return self.func(*args, **kwargs)
    

    I would make it this way:

    resultDone = False
    result = None
    try:
      if key in self.cache: return self.cache[key]
      else:
        result = self.func(*args, **kwargs)
        resultDone = True
        self.cache[key] = result
    except TypeError: # unhashable key
      pass
    if 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.

    0 讨论(0)
提交回复
热议问题