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 тоже.