1

I have been trying to make functools.lru_cache instance specific as described in this answer, but their solution fails when used on the __call__ method.

class test:
    def __init__(self):
        self.method = lru_cache()(self.method)
        self.__call__ = lru_cache()(self.__call__)

    def method(self, x):
        print('method', end=' ')
        return x

    def __call__(self, x):
        print('__call__', end=' ')
        return x

b = test()
# b.method is cached as expected
print(b.method(1)) # method 1
print(b.method(1)) # 1

# __call__ is executed every time
print(b(1)) # __call__ 1
print(b(1)) # __call__ 1

So the results of __call__ are not getting cached when wrapped using this method. The cache on __call__ does not even register the function having been called, and unhashable values do not throw errors.

print(b.method.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
print(b.__call__.cache_info())
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)

print(b.call({})) # __call__ {}
print(b.method({})) # ... TypeError: unhashable type: 'dict'
Will
  • 415
  • 8
  • 15
  • 1
    i have tried you code, and if applying `@lru_cache` decorator to `__call__` , it works fine, but for you case, you are assigning `__call__` to self when initialize, so the lru version of `__call__` is **added** in `self.__dict__`, without actually changing `__call__` method. – Enix Nov 22 '18 at 04:37
  • @Enix If you mean decorating `__call__` normally then yes that works fine for one instance, but as soon as you have two instances then the cache is shared, and resetting it on one resets it on the other instance, so using the decorator normally does not work. (I have tried not to make this question about how to have instance specific caches, since the linked question covers that) – Will Nov 22 '18 at 18:22

2 Answers2

2

This is due to the difference between class attributes and instance attributes. When accessing an attribute (such as method) python first checks for an instance attribute. If you have not assigned to self.method it will not find one. Then class attributes are checked, which is equivalent to self.__class__.method. The value of this function is not changed by assigning to self.method, that only updates the instance attribute.

However, b(1) becomes b.__class__.__call__(b, 1) which uses the original class definition of __call__ and b.__call__(1) will be cached the same way as method since it uses the instance definition.

Will
  • 415
  • 8
  • 15
-1

The original answer is really good.

I am attaching another solution of the problem. methodtools.lru_cache will work as you expect.

from methodtools import lru_cache

class test:
    @lru_cache
    def __call__(self, x):
        print('__call__', end=' ')
        return x

It requires to install methodtools via pip:

pip install methodtools

youknowone
  • 919
  • 6
  • 14