web-dev-qa-db-ja.com

Python functools lru_cache with class methods:release object

メモリをリークすることなく、クラス内でfunctoolsのlru_cacheをどのように使用できますか?次の最小限の例では、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を使用する推奨方法は何ですか?

私は2つの回避策を知っています: インスタンスキャッシュごとに使用 または キャッシュにオブジェクトを無視させる (ただし、誤った結果になる可能性があります)

39
televator

これは最もクリーンなソリューションではありませんが、プログラマーには完全に透過的です。

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とまったく同じパラメーターを受け取り、まったく同じように機能します。ただし、selflru_cacheに渡すことはなく、インスタンスごとにlru_cacheを使用します。

32
orlp

この使用例では、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を返します。

7
youknowone

python 3.8はfunctoolsモジュールに cached_property デコレーターを導入しました。テストすると、インスタンスが保持されないようです。

python 3.8に更新したくない場合は、 ソースコード を使用できます。必要なのは、RLockをインポートして_NOT_FOUNDを作成することだけです。オブジェクト。意味:

from threading import RLock

_NOT_FOUND = object()

class cached_property:
    # https://github.com/python/cpython/blob/master/Lib/functools.py#L913
    ...
1
moshevi