Comment puis-je utiliser lru_cache de functools à l'intérieur des classes sans fuite de mémoire? Dans l'exemple minimal suivant, l'instance foo
ne sera pas publiée bien qu'elle soit hors de portée et sans référence (autre que 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()
Mais foo
et donc foo.big
(a BigClass
) sont toujours en vie
import gc; gc.collect() # collect garbage
len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]) # is 1
Cela signifie que les instances Foo/BigClass résident toujours en mémoire. Même la suppression de Foo
(del Foo
) ne les libérera pas.
Pourquoi lru_cache conserve-t-il l'instance? Le cache n'utilise-t-il pas du hachage et non l'objet réel?
Quelle est la façon recommandée d'utiliser lru_caches dans les classes?
Je connais deux solutions: tiliser des caches par instance ou faire en sorte que le cache ignore l'objet (ce qui pourrait conduire à des résultats erronés, cependant)
Ce n'est pas la solution la plus propre, mais elle est entièrement transparente pour le programmeur:
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
Il prend exactement les mêmes paramètres que lru_cache
, et fonctionne exactement de la même manière. Cependant, il ne passe jamais de self
à lru_cache
et utilise à la place une instance par lru_cache
.
Je vais introduire methodtools
pour ce cas d'utilisation.
pip install methodtools
à installer https://pypi.org/project/methodtools/
Ensuite, votre code fonctionnera simplement en remplaçant functools par methodtools.
from methodtools import lru_cache
class Foo:
@lru_cache(maxsize=16)
def cached_method(self, x):
return x + 5
Bien sûr, le test gc renvoie également 0 également.
python 3.8 a introduit le cached_property
décorateur dans le module functools
. lorsqu'il est testé, il semble ne pas conserver les instances.
Si vous ne voulez pas mettre à jour vers python 3.8, vous pouvez utiliser le code source . Tout ce dont vous avez besoin est d'importer RLock
et de créer le _NOT_FOUND
objet. sens:
from threading import RLock
_NOT_FOUND = object()
class cached_property:
# https://github.com/python/cpython/blob/master/Lib/functools.py#L913
...