Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python functools lru_cache with class methods: release object

How can I use functools.lru_cache inside classes without leaking memory?

In the following minimal example the foo instance won't be released although going out of scope and having no referrer (other than the lru_cache).

from functools import lru_cache class BigClass:     pass class Foo:     def __init__(self):         self.big = BigClass()     @lru_cache(maxsize=16)     def cached_method(self, x):         return x + 5  def fun():     foo = Foo()     print(foo.cached_method(10))     print(foo.cached_method(10)) # use cache     return 'something'  fun() 

But foo and hence foo.big (a BigClass) are still alive

import gc; gc.collect()  # collect garbage len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]) # is 1 

That means that Foo/BigClass instances are still residing in memory. Even deleting Foo (del Foo) will not release them.

Why is lru_cache holding on to the instance at all? Doesn't the cache use some hash and not the actual object?

What is the recommended way use lru_caches inside classes?

I know of two workarounds: Use per instance caches or make the cache ignore object (which might lead to wrong results, though)

like image 848
televator Avatar asked Nov 12 '15 13:11

televator


People also ask

What does Functools lru_cache do?

Python's functools module comes with the @lru_cache decorator, which gives you the ability to cache the result of your functions using the Least Recently Used (LRU) strategy. This is a simple yet powerful technique that you can use to leverage the power of caching in your code.

How do you use Functools in Python?

It can be used in key functions such as sorted(), min(), max(). It applies a function of two arguments repeatedly on the elements of a sequence so as to reduce the sequence to a single value. For example, reduce(lambda x, y: x^y, [1, 2, 3, 4]) calculates (((1^2)^3)^4) .

How do you cache a function in Python?

To memoize a function in Python, we can use a utility supplied in Python's standard library—the functools. lru_cache decorator. Now, every time you run the decorated function, lru_cache will check for a cached result for the inputs provided. If the result is in the cache, lru_cache will return it.

Is Functools a standard Python library?

Introduction. The functools module, part of Python's standard Library, provides useful features that make it easier to work with high order functions (a function that returns a function or takes another function as an argument ).


2 Answers

This is not the cleanest solution, but it's entirely transparent to the programmer:

import functools import weakref  def memoized_method(*lru_args, **lru_kwargs):     def decorator(func):         @functools.wraps(func)         def wrapped_func(self, *args, **kwargs):             # We're storing the wrapped method inside the instance. If we had             # a strong reference to self the instance would never die.             self_weak = weakref.ref(self)             @functools.wraps(func)             @functools.lru_cache(*lru_args, **lru_kwargs)             def cached_method(*args, **kwargs):                 return func(self_weak(), *args, **kwargs)             setattr(self, func.__name__, cached_method)             return cached_method(*args, **kwargs)         return wrapped_func     return decorator 

It takes the exact same parameters as lru_cache, and works exactly the same. However it never passes self to lru_cache and instead uses a per-instance lru_cache.

like image 113
orlp Avatar answered Sep 22 '22 03:09

orlp


I will introduce methodtools for this use case.

pip install methodtools to install https://pypi.org/project/methodtools/

Then your code will work just by replacing functools to methodtools.

from methodtools import lru_cache class Foo:     @lru_cache(maxsize=16)     def cached_method(self, x):         return x + 5 

Of course the gc test also returns 0 too.

like image 24
youknowone Avatar answered Sep 22 '22 03:09

youknowone