具有实例方法的Python functools lru_cache:释放对象

新手上路,请多包涵

如何在不泄漏内存的情况下在类内部使用 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 的推荐方法是什么?

我知道有两种解决方法: 使用每个实例缓存使缓存忽略对象(但这可能会导致错误的结果)

原文由 televator 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 545
2 个回答

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

 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

原文由 orlp 发布,翻译遵循 CC BY-SA 3.0 许可协议

简单的包装解决方案

这是一个包装器,它将保持对该实例的弱引用:

 import functools
import weakref

def weak_lru(maxsize=128, typed=False):
    'LRU Cache decorator that keeps a weak reference to "self"'
    def wrapper(func):

        @functools.lru_cache(maxsize, typed)
        def _func(_self, *args, **kwargs):
            return func(_self(), *args, **kwargs)

        @functools.wraps(func)
        def inner(self, *args, **kwargs):
            return _func(weakref.ref(self), *args, **kwargs)

        return inner

    return wrapper

例子

像这样使用它:

 class Weather:
    "Lookup weather information on a government website"

    def __init__(self, station_id):
        self.station_id = station_id

    @weak_lru(maxsize=10)
    def climate(self, category='average_temperature'):
        print('Simulating a slow method call!')
        return self.station_id + category

何时使用

由于 weakrefs 增加了一些开销,您只希望在实例很大并且应用程序不能等待较旧的未使用调用从缓存中老化时使用它。

为什么这样更好

与其他答案不同,我们只有一个类缓存,而不是每个实例一个。如果您想从最近最少使用的算法中获益,这一点很重要。每个方法使用一个缓存,您可以设置 maxsize ,以便无论活动实例的数量如何,总内存使用量都是有限的。

处理可变属性

如果方法中使用的任何属性是可变的,请务必添加 __ eq () 和 __ hash () 方法:

 class Weather:
    "Lookup weather information on a government website"

    def __init__(self, station_id):
        self.station_id = station_id

    def update_station(station_id):
        self.station_id = station_id

    def __eq__(self, other):
        return self.station_id == other.station_id

    def __hash__(self):
        return hash(self.station_id)

原文由 Raymond Hettinger 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题