Python functools lru_cache with instance methods: release object 使用实例方法的Python functools lru_cache:释放对象

17 浏览
0 Comments

Python functools lru_cache with instance methods: release object 使用实例方法的Python functools lru_cache:释放对象

如何在类中使用functools.lru_cache而不会造成内存泄漏?\n在下面的最简示例中,尽管foo实例已经超出范围并且没有引用者(除了lru_cache),但它不会被释放。\n

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)) # 使用缓存
    return 'something'
fun()

\n但是foo以及foo.big(一个BigClass)仍然存在。\n

import gc; gc.collect()  # 收集垃圾
len([obj for obj in gc.get_objects() if isinstance(obj, Foo)]) # 为1

\n这意味着Foo/BigClass实例仍然驻留在内存中。即使删除Foo(del Foo),它们也不会被释放。\n为什么lru_cache会保留实例呢?难道缓存使用的不是实际对象而是某种哈希吗?\n在类中使用lru_cache的推荐方式是什么?\n我知道两个解决方法:\n使用每个实例的缓存使缓存忽略对象(尽管可能导致错误结果)

0
0 Comments

Python functools lru_cache with instance methods: release object

在使用Python编程过程中,有时候需要在实例方法中使用lru_cache进行缓存,以提高程序的性能。然而,使用functools.lru_cache装饰实例方法时,会出现一个问题:释放对象。当实例方法被装饰为lru_cache后,对象无法被垃圾回收并释放,导致内存泄漏。

为了解决这个问题,可以使用methodtools模块。首先,通过pip install methodtools安装methodtools。然后,将代码中的functools替换为methodtools即可。

下面是一个示例代码:

from methodtools import lru_cache
class Foo:
    _cache(maxsize=16)
    def cached_method(self, x):
        return x + 5

使用methodtools.lru_cache装饰实例方法时,每个类实例都有一个独立的缓存存储空间,这样就可以避免释放对象的问题。相比之下,使用functools.lru_cache装饰实例方法时,所有实例共享同一个缓存存储空间,会导致对象无法被释放。

当然,也可以使用ring.lru来实现类似的功能。不过,methodtools.lru_cache与functools.lru_cache的行为完全一致,只是内部实现方式不同。而ring.lru在Python中重新实现了lru存储,提供了更多功能。

为了解决实例方法中lru_cache释放对象的问题,可以使用methodtools模块,并将functools替换为methodtools。这样,每个实例都有独立的缓存存储空间,避免了对象无法释放的情况发生。

0
0 Comments

Python functools lru_cache with instance methods: release object

在使用Python的functools库中的lru_cache装饰器时,对于实例方法存在释放对象的问题。本文提供了一种解决方法。

问题的出现原因是,当使用lru_cache装饰器时,它会缓存方法的结果,以便在下次调用时可以直接返回结果,而不需要重新计算。然而,对于实例方法来说,如果缓存了方法的结果,那么实例对象将无法被垃圾回收,因为缓存会持有对实例对象的引用。这就导致了一个问题:当实例对象不再被使用时,无法被释放,从而造成内存泄漏。

为了解决这个问题,可以使用一个简单的包装器来保持对实例对象的弱引用。具体实现如下:

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

上述代码中,`weak_lru`是一个装饰器函数,它接受`maxsize`和`typed`两个参数,并返回一个包装器函数`wrapper`。在`wrapper`函数内部,使用了`functools.lru_cache`装饰器来实现LRU缓存功能。然后,定义了一个内部函数`inner`,它接受实例对象作为第一个参数,并将其弱引用传递给缓存的方法。最后,将`inner`函数返回作为包装器的结果。

使用这个解决方案的示例代码如下:

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

上述代码中,`Weather`类的`climate`方法被应用了`weak_lru`装饰器,并设置了最大缓存大小为10。这样,每次调用`climate`方法时,会先检查缓存中是否已经存在结果,如果存在则直接返回,否则执行方法体并将结果缓存起来。

对于可变的属性,还需要添加`__eq__()`和`__hash__()`方法,以确保缓存的正确性。示例代码如下:

class Weather:
    "Lookup weather information on a government website"
    def __init__(self, station_id):
        self.station_id = station_id
    
    def update_station(self, 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)

通过以上的解决方法,可以解决使用Python functools库中的lru_cache装饰器时,实例方法释放对象的问题。这样可以避免内存泄漏,并提高程序的性能和效率。

0
0 Comments

Python functools模块中的lru_cache装饰器可以用于缓存函数的结果,以提高函数的执行效率。然而,在使用lru_cache装饰器时,如果需要在类的实例方法上使用装饰器,会遇到一些问题。这篇文章将讨论这个问题的出现原因以及解决方法。

在给实例方法使用lru_cache装饰器时,我们需要注意到一个问题:如果我们直接将self参数传递给lru_cache装饰器,会导致实例对象无法被销毁,从而引发内存泄漏。为了解决这个问题,我们可以使用一个弱引用来存储self参数,从而避免强引用导致的内存泄漏。

具体的解决方法如下:

首先,我们需要导入functools和weakref模块。

然后,我们定义一个装饰器函数memoized_method,它接受与lru_cache相同的参数,并返回一个装饰器。

在memoized_method装饰器内部,我们定义一个wrapped_func函数,它接受self参数以及被装饰的方法的其他参数。

我们使用weakref.ref函数创建一个对self的弱引用self_weak。

然后,我们使用lru_cache装饰器来装饰一个内部函数cached_method。注意,我们没有将self_weak参数传递给lru_cache装饰器,而是将self_weak作为第一个参数传递给被装饰的方法func。

接下来,我们使用setattr函数将cached_method设置为self的一个属性,这样当我们再次调用实例方法时,就会调用缓存的版本。

最后,我们返回cached_method的调用结果。

使用这个装饰器,我们可以在类的实例方法上使用lru_cache装饰器,从而实现对方法执行结果的缓存。

然而,这种解决方法并不适用于特殊方法(如__getitem__),因为特殊方法是通过类的槽(slot)进行查找的,而不是通过实例字典进行查找。因此,我们无法使用lru_cache装饰器来缓存特殊方法的执行结果。

在Python 3.x版本中,由于语法的变化,需要对解决方法进行一些修改。在Python 3.x中,我们需要在wrapped_func函数的定义中添加一个参数self,以避免出现"TypeError: wrapped_func() missing 1 required positional argument: 'self'"的错误。

总结起来,通过使用上述解决方法,我们可以在类的实例方法上使用lru_cache装饰器来缓存方法的执行结果,提高方法的执行效率。然而,需要注意的是,这种解决方法不适用于特殊方法,并且在Python 3.x中需要进行一些修改。

0