Python lru_cache使用详解
介绍
在使用Python开发中,我们经常会遇到需要缓存结果的场景。缓存可以大大提高程序的运行效率,特别是对于一些计算量较大的函数,通过缓存可以避免无效的重复计算。Python的标准库functools中提供了一个装饰器lru_cache
,用于实现缓存功能。lru_cache
是Least Recently Used的缩写,表示最近最少使用算法,它会根据函数的最近使用情况来决定缓存的数据。
本文将详细介绍lru_cache
的使用方法,以及其实现原理。
lru_cache的基本使用
lru_cache
装饰器可以用于任意可调用对象(callable)。下面的例子演示了如何使用lru_cache
来缓存一个函数的结果。
from functools import lru_cache
@lru_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 输出 55
上面的代码定义了一个菲波拉契数列的函数fibonacci
,它使用了lru_cache
来缓存中间结果。在第一次调用fibonacci
函数时,lru_cache
会将n
作为参数,同时将函数的返回结果与n
一起作为key,将结果存储在一个缓存字典中。如果再次调用fibonacci
函数时,如果参数n
与之前调用时的值一致,那么lru_cache
会直接返回缓存的结果,而不会再次计算。
需要注意的是,lru_cache
只能缓存不可变的参数,如果函数的参数是可变对象,那么结果会出现错误。
lru_cache的参数
lru_cache
装饰器可以接收多个可选参数,用于调整缓存的行为。
maxsize
maxsize
参数用于指定缓存的大小,即最多保存的缓存条目个数。默认值为128
。当缓存的条目数超过maxsize
时,旧的缓存项会被淘汰掉,为新的缓存项腾出空间。
from functools import lru_cache
@lru_cache(maxsize=2)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 输出 55
print(fibonacci.cache_info()) # 输出 CacheInfo(hits=8, misses=11, maxsize=2, currsize=2)
上面的代码中,lru_cache
的maxsize
参数被设置为2
,即最多只缓存两个结果。在这种情况下,当我们调用fibonacci(10)
时,lru_cache
只会保留最近计算过的两个结果。通过调用fibonacci.cache_info()
可以查看缓存的信息,其中hits
表示命中缓存的次数,misses
表示未命中缓存的次数,maxsize
表示缓存的最大容量,currsize
表示当前缓存的条目数。
typed
typed
参数用于指定是否对不同参数类型的结果进行分别缓存。如果将typed
参数设为True
,那么不同的参数类型会分别缓存结果。
from functools import lru_cache
@lru_cache(typed=True)
def power(base, exponent):
return base ** exponent
print(power(2, 3)) # 输出 8
print(power(2.0, 3)) # 输出 8.0
print(power.cache_info()) # 输出 CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
上面的代码中,power
函数接受两个参数base
和exponent
,lru_cache
的typed
参数设为True
。首先我们调用power(2, 3)
,然后再调用power(2.0, 3)
。尽管在数值上两次调用的结果是一样的,但是由于参数类型不同,所以结果没有被缓存。通过调用power.cache_info()
可以查看缓存的信息,其中misses
为2
表示两次调用都没有命中缓存。
连续使用lru_cache
可以将lru_cache
装饰器连续使用,这样可以为同一个函数设置多个缓存。下面的例子演示了如何设置两个缓存。
from functools import lru_cache
@lru_cache(maxsize=2)
@lru_cache(maxsize=4)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # 输出 55
print(fibonacci.cache_info()) # 输出 CacheInfo(hits=24, misses=11, maxsize=4, currsize=4)
print(fibonacci.cache_info().__reduce__()[1]) # 输出 (24, 11, 4, 4)
在上面的代码中,fibonacci
函数被lru_cache
装饰器连续装饰了两次,分别设置了两个不同的缓存。在这种情况下,缓存的命中情况和缓存的信息都是分别计算的。通过调用fibonacci.cache_info()
可以查看最底层的缓存信息,即第一个被装饰的缓存。通过调用fibonacci.cache_info().__reduce__()[1]
可以查看所有缓存的命中次数。
lru_cache的实现原理
lru_cache
的实现基于哈希表和双向链表。
在缓存结果时,lru_cache
使用了一个哈希表(字典)来存储结果。哈希表的键是调用函数时的参数,哈希表的值是函数的返回结果。通过利用哈希表的快速查找特性,可以快速地找到结果。
同时,lru_cache
使用了一个双向链表来记录缓存结果的使用顺序。链表的头部代表最近使用的缓存结果,链表的尾部代表最近最少使用的缓存结果。当需要淘汰缓存结果时,lru_cache
会从链表的尾部开始淘汰,即淘汰最近最少使用的结果。
在每次调用函数时,lru_cache
会通过查找哈希表快速获取结果,然后将结果从链表中提到链表的头部,表示该结果刚刚被使用过。
需要注意的是,lru_cache
使用了Python的垃圾回收机制,当一个缓存结果对象没有被其他对象引用时,它会被自动从缓存中移除,并释放内存。
lru_cache的应用场景
lru_cache
在很多场景下都能够提升程序的性能。下面列举了一些适合使用lru_cache
的应用场景。
递归函数优化
递归函数的特点是可以将一个大问题拆分成多个小问题来解决,但是这种分解可能会导致大量的重复计算。使用lru_cache
可以避免重复计算,从而提高递归函数的性能。
from functools import lru_cache
@lru_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(30)) # 输出 832040
上面的代码中,fibonacci
函数是一个经典的递归函数,计算斐波那契数列。由于递归的特性,如果不缓存中间结果,计算fibonacci(30)
的时间会非常长。但是通过使用lru_cache
,我们可以避免重复计算,从而大大提高性能。
I/O操作的结果缓存
在进行一些I/O操作时,比如访问数据库或者调用API接口,结果的获取可能需要花费较长的时间。为了避免频繁访问这些I/O操作,可以使用lru_cache
来缓存结果,以提高程序的响应速度。
from functools import lru_cache
import requests
@lru_cache
def get_data(url):
response = requests.get(url)
return response.text
print(get_data("https://www.example.com")) # 输出网页内容
print(get_data("https://www.example.com")) # 直接从缓存中获取结果
上面的代码中,get_data
函数用于获取一个网页的内容。由于访问网页需要花费一定的时间,为了避免频繁访问网页,我们使用lru_cache
来缓存结果。第一次调用get_data
时,结果会被缓存下来,第二次调用时,结果会直接从缓存中返回,而不需要再次进行访问。
总结
本文详细介绍了Python的lru_cache
装饰器的使用方法和实现原理。通过缓存函数的结果,可以提高程序的性能,避免重复计算。lru_cache
在递归函数优化和I/O操作结果缓存等场景下都能发挥重要作用。在使用lru_cache
时,可以通过调整maxsize
和typed
等参数来适应不同的需求。