11

I have functions in python that have caches with lru_cache e.g.

 @lru_cache(maxsize=None)
 def my_function():
    ...

While i can individually clear the caches with e.g. my_function.cache_clear() is there any way of clearing the caches of every function at once? [I was thinking that maybe there is a way of returning all function names loaded in memory, and then loop over them clearing the cache from each].

I'm specifically looking to implement as part of a fall-back, for cases where say 90% of the memory on my machine gets used.

kyrenia
  • 5,431
  • 9
  • 63
  • 93
  • Yes, the decorated function now has the method `my_function.cache_clear()`. You can also get stats with `my_function.cache_info()`. See [`lru_cache`](https://docs.python.org/3.5/library/functools.html?highlight=lru#functools.lru_cache) – AChampion Oct 26 '16 at 23:38
  • @AChampion - sure, aware of both of those, question though is rather is there a way of applying those methods to every decorated functions (i.e. clear *all* the lru_cache's). – kyrenia Oct 26 '16 at 23:54
  • No, there is no simple way to clear all decorated function caches, they are all independent. You could create a registry of all those functions and then iterate through them to clear. – AChampion Oct 27 '16 at 00:18

2 Answers2

17

maybe there is a way of returning all function names loaded in memory, and then loop over them clearing the cache from each

Yes that is possible as well:

import functools
import gc

gc.collect()
wrappers = [
    a for a in gc.get_objects() 
    if isinstance(a, functools._lru_cache_wrapper)]

for wrapper in wrappers:
    wrapper.cache_clear()

I'm specifically looking to implement as part of a fall-back, for cases where say 90% of the memory on my machine gets used.

This is code you will probably not use under normal circumstances, but can be useful for debugging.

In my particular case I wanted to clear some dangling file handles from matplotlib (in order to focus on my own loose file handles), so it was not possible to use the solution by Alex Hall.

BlackShift
  • 2,296
  • 2
  • 19
  • 27
  • Inheriting a codebase that was so hard to test because of the reliance on lru_caches that shared state between tests. – Rol Feb 18 '21 at 10:27
12

You can create a modified decorator which also takes note of the cached functions:

cached_functions = []

def clearable_lru_cache(*args, **kwargs):
    def decorator(func):
        func = lru_cache(*args, **kwargs)(func)
        cached_functions.append(func)
        return func

    return decorator

def clear_all_cached_functions():
    for func in cached_functions:
        func.cache_clear()

If you really want you can also use monkey patching to replace the original decorator.

Test:

@clearable_lru_cache()
def foo(x):
    print('foo', x)

@clearable_lru_cache()
def bar(x):
    print('bar', x)

for i in [1, 2]:
    for j in [1, 2]:
        print('Calling functions')
        for k in [1, 2, 3]:
            for f in [foo, bar]:
                f(k)
        print('Functions called - if you saw nothing they were cached')
    print('Clearing cache')
    clear_all_cached_functions()

Output:

Calling functions
foo 1
bar 1
foo 2
bar 2
foo 3
bar 3
Functions called - if you saw nothing they were cached
Calling functions
Functions called - if you saw nothing they were cached
Clearing cache
Calling functions
foo 1
bar 1
foo 2
bar 2
foo 3
bar 3
Functions called - if you saw nothing they were cached
Calling functions
Functions called - if you saw nothing they were cached
Clearing cache
Alex Hall
  • 34,833
  • 5
  • 57
  • 89