Python functools lru_cache с помощью методов класса: объект выпуска

Как я могу использовать классы lru_cache functools внутри классов без утечки памяти? В следующем минимальном примере экземпляр foo не будет выпущен, хотя выходит за рамки и не имеет реферера (кроме 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()

Но foo и, следовательно, foo.big (a BigClass) все еще живы

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

Это означает, что экземпляры Foo/BigClass все еще находятся в памяти. Даже удаление foo (del foo) не освободит их.

Почему lru_cache поддерживает экземпляр вообще? Не использует ли кеш какой-то хэш, а не фактический объект?

Каков рекомендуемый способ использования lru_caches внутри классов?

Я знаю два обходных пути: Использовать в кэшах экземпляров или сделать объект игнорирования кэша (что может привести к неправильным результатам)

Ответы

Ответ 1

Это не самое чистое решение, но оно полностью прозрачно для программиста:

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

Он принимает те же параметры, что и lru_cache, и работает точно так же. Однако он никогда не передает self в lru_cache и вместо этого использует per-instance lru_cache.

Ответ 2

Я представлю methodtools для этого methodtools использования.

pip install methodtools для установки https://pypi.org/project/methodtools/

Тогда ваш код будет работать, просто заменив functools на methodtools.

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

Конечно, тест gc также возвращает 0 тоже.