Search code examples
python-3.xfunctioncachingglobalmemoization

Global cache with specific object attributes as input in python


In my attempt to achieve a class level cache decorator, I have stumbled across the ring library which is able to save a function's inputs an outputs globally. It basically allows me to achieve the following behavior, given the class definition of Calculate as below:

import ring

class Calculate:

    @ring.dict({})
    def sum(self, a, b):
       print('actually calculating')
       sum = a + b
       return sum

this is the behavior I am trying to achieve with Calculate

>>> calculate = Calculate()
>>> calcualte.sum(5,7)
actually calculating
12

>>> different_calculate = Calculate()
>>> different_calculate.sum(5,7)
12    #this outputs 12 straight from the cache from first calculate. 
#Note that even if 'different_calculate' and 'calculate' are different instantiations of 'Calculate', the cache works at a class level. Therefore the sum is not actually REcalculated.

Now, I need to achieve the same behavior with sum, this time being a property. The issue that I am facing here is that in a @property def takes as argument self. Therefore, when trying to cache as in the previous example it will not work, because the input is no longer num1 and num2, but self, which changes with every instantiation. Therefore it will never pull from cache on a different class instantiation, as self always changes with every instantiation. See below:

import ring

class Calculate:
   def __init__(self, num1, num2):
      self.num1 = num1
      self.num2 = num2

    @ring.dict({})
    @property
    def sum(self):
       print('actually calculating')
       sum = num1 + num2
       return sum

>>> calculate = Calculate()
>>> calcualte.sum(5,7)
actually calculating
12

>>> different_calculate = Calculate()
>>> different_calculate.sum(5,7)
actually calculating
12

To solve this problem, I somehow have to tell the cache library not to look at self as input, but at self.num1, self.num2. The code will then nee to be something like this:

   @ring.dict(self.num1, self.num2)  # <--- this does not exist
    @property
    def sum(self):
       print('actually calculating')
       sum = num1 + num2
       return sum

Is there any way I can do this with ring, or any other python cache library for that matter?


Solution

  • So after getting some inspiration from here, I ended up just creating my own memoization function, as so:

    memo = {}
    
    def memoize(*attrs):  
        def wrapper(f):  
            def helper(obj):
                key = tuple([getattr(obj,attr) for attr in attrs])
                if key not in memo:            
                    memo[key] = f(obj)
                return memo[key]
            return helper
        return wrapper
    
    
    
    
    class Calculate:
        def __init__(self, num1, num2):
            self.num1 = num1
            self.num2 = num2
    
        @property
        @memoize('num1', 'num2')  
        def sum(self):
            print('actually calculating')
            sum = self.num1 + self.num2
            return sum
    
    
    >>> calculate = Calculate(6,9)
    >>> calculate.sum
    actually calculating
    15
    
    >>> another_calculate = Calculate(6,9)
    >>> another_calculate.sum
    15