web-dev-qa-db-fra.com

Python functools lru_cache avec les méthodes de classe: release object

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)

39
televator

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.

32
orlp

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.

7
youknowone

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
    ...
1
moshevi