说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 内置模块 functools 的一个高阶函数 @lru_cache 是一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或 I/O 函数的调用时间。
@lru_cache 的用法有两种形式:
from functools import lru_cache
lru_cache(user_function)
@lru_cache(maxsize=128, typed=False)
def user_function(x):
pass
它实现了 最近最少使用(Least-recently-used)缓存装饰器,缓存函数的参数必须是可哈希的。最近最少使用的机制是如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小, LRU算法选择将最近最少使用的数据淘汰,保留那些经常被命中的数据。
以下是它的几个参数的说明:
被装饰的函数会成为 functools._lru_cache_wrapper
对象,它有以下属性和方法:
f.cache_parameters()
:缓存参数f.cache_info()
:当前缓存情况,返回一个 namedtuple,包含缓存使用、未使用、长度、最大长度,如 CacheInfo(hits=2, misses=1, maxsize=128, currsize=1)f.cache_clear()
:清除缓存f.__wrapped__()
:未被装饰的参数如果想使用未补装饰的参数可以用 f.__wrapped__()
。
LRU(最久未使用算法)缓存 在最近的调用是即将到来的调用的最佳预测值时性能最好(例如,新闻服务器上最热门文章倾向于每天更改)。 缓存的大小限制可确保缓存不会在长期运行进程如网站服务器上无限制地增长。
一般来说,LRU 缓存只在当你想要重用之前计算的结果时使用。因此,用它缓存具有副作用的函数、需要在每次调用时创建不同、易变的对象的函数或者诸如 time() 或 random() 之类的不纯函数是没有意义的。
由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的,针对于列表等不可哈希的对象的时候是不可用的。
缓存会保持对参数的引用并返回值,直到它们结束生命期退出缓存或者直到缓存被清空。
不同模式的参数可能被视为不同从而产生多个缓存项,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。
允许 lru_cache 装饰器被直接应用于一个用户自定义函数,让 maxsize 保持其默认值 128 个元素。
被包装的函数配有一个 cache_parameters() 函数,该函数返回一个新的 dict 用来显示 maxsize 和 typed 的值,这只是出于显示信息的目的,对缓存功能没有任何影响。
为了帮助衡量缓存的有效性以及调整 maxsize 形参,被包装的函数会带有一个 cache_info() 函数,它返回一个 named tuple 以显示 hits, misses, maxsize 和 currsize。
该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear() 。
原始的未经装饰的函数可以通过 __wrapped__
属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。
以下是一个计算英文语句中元音字母的数量的方法,由于会频率调用计算,每次进行缓存,对于之前计算过的直接从缓存读取。代码如下:
from functools import lru_cache
@lru_cache
def count_vowels(sentence):
return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
# 执行3次
count_vowels('hello')
# 2
执行三次函数调用,第二次和第三次会使用缓存,可参考下文关于方法的介绍了解查看缓存使用情况。
静态 Web 内容的 LRU 缓存示例:
from functools import lru_cache
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'https://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
以下是使用缓存通过 动态规划 计算 斐波那契数列 的例子。
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
被装饰的函数会有几个方法,以上例我们进行查看:
from functools import lru_cache
@lru_cache
def count_vowels(sentence):
return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
count_vowels
# <functools._lru_cache_wrapper at 0x7fdb30de8300>
# 缓存参数
count_vowels.cache_parameters()
# {'maxsize': 128, 'typed': False}
# 缓存信息初始量
count_vowels.cache_info()
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
# 执行3次
count_vowels('hello')
# 2
# 使用未被封装的原函数(不使用缓存,也不缓存)
count_vowels.__wrapped__('hello')
# 2
# 缓存被使用两次,一次没用(第一次执行时),大小为 1
count_vowels.cache_info()
# CacheInfo(hits=2, misses=1, maxsize=128, currsize=1)
# 清除缓存
count_vowels.cache_clear()
# 无返回 None
# 再看看缓存情况
count_vowels.cache_info()
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
关于 @lru_cache 的重要迭代有:
更新时间:2022-01-19 15:52:46 标签:python cache 缓存 装饰器