带有实例方法的Python函数工具lru_cache:Release对象

问题描述

如何在不泄漏内存的情况下在类内使用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(aBigClass)仍然有效

import gc; gc.collect()  # collect garbage
len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]) # is 1

这意味着Foo/BigClass实例仍驻留在内存中。即使删除Foo(delFoo)也不会释放它们。

为什么lru_cache要保留实例?缓存不是使用某些哈希而不是实际对象吗?

建议在类内使用lru_cache的方式是什么?

我知道两种解决方法: Use per instance caches或make the cache ignore object(但可能导致错误结果)


解决方案

这不是最干净的解决方案,但对程序员完全透明:

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,而是使用每个实例的lru_cache

相关文章