tel*_*tor 33 python caching lru functools python-decorators
如何在不泄漏内存的情况下在类中使用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()
Run Code Online (Sandbox Code Playgroud)
但是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
Run Code Online (Sandbox Code Playgroud)
这意味着Foo/BigClass实例仍然驻留在内存中.即使删除Foo(del Foo)也不会释放它们.
为什么lru_cache会依赖实例?缓存不是使用一些哈希而不是实际对象吗?
在类中使用lru_caches的推荐方法是什么?
orl*_*rlp 30
这不是最干净的解决方案,但它对程序员来说完全透明:
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
Run Code Online (Sandbox Code Playgroud)
它采用完全相同的参数lru_cache,并且工作原理完全相同.但是它永远不会传递self给lru_cache而是使用每个实例lru_cache.
Ray*_*ger 30
这是一个包装器,它将保留对实例的弱引用:
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
Run Code Online (Sandbox Code Playgroud)
像这样使用它:
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
Run Code Online (Sandbox Code Playgroud)
由于弱引用会增加一些开销,因此您只想在实例很大并且应用程序无法等待较旧的未使用调用从缓存中过期时才使用它。
与其他答案不同,我们只有一个类缓存,而不是每个实例都有一个缓存。如果您想从最近最少使用的算法中获得一些好处,这一点很重要。通过每个方法一个缓存,您可以设置 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)
Run Code Online (Sandbox Code Playgroud)
pab*_*loi 22
解决此问题的一个更简单的解决方案是在构造函数中而不是在类定义中声明缓存:
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
Run Code Online (Sandbox Code Playgroud)
you*_*one 18
我将介绍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
Run Code Online (Sandbox Code Playgroud)
当然 gc 测试也返回 0。
此方法的问题在于这self是一个未使用的变量。
简单的解决方案是将方法变成静态方法。这样,该实例就不是缓存的一部分。
class Foo:
def __init__(self):
self.big = BigClass()
@staticmethod # <-- Add this line
@lru_cache(maxsize=16)
def cached_method(x):
print('miss')
return x + 5
Run Code Online (Sandbox Code Playgroud)
python 3.8cached_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
...
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
9202 次 |
| 最近记录: |