66

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_cacheクラス内で s を使用する推奨される方法は何ですか?

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

4

5 に答える 5

42

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

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

于 2015-11-12T13:26:14.203 に答える
8

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

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

from threading import RLock

_NOT_FOUND = object()

class cached_property:
    # https://github.com/python/cpython/blob/v3.8.0/Lib/functools.py#L930
    ...
于 2019-10-28T14:18:03.013 に答える
5

この問題に対するさらに簡単な解決策は、クラス定義ではなくコンストラクターでキャッシュを宣言することです。

from functools import lru_cache
import gc

class BigClass:
    pass
class Foo:
    def __init__(self):
        self.big = BigClass()
        self.cached_method = lru_cache(maxsize=16)(self.cached_method)
    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'
    
if __name__ == '__main__':
    fun()
    gc.collect()  # collect garbage
    print(len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]))  # is 0
于 2021-07-27T18:41:41.320 に答える