一文带你深入理解Python的`functools.lru_cache`装饰器
作者:小小张说故事
一、什么是 functools.lru_cache?
functools.lru_cache
是 Python 标准库中 functools
模块的一部分。lru_cache
装饰器可以用来为一个函数添加一个缓存系统。这个缓存系统会存储函数的输入和对应的输出。如果函数被调用,并且给出了已经缓存过的输入,那么函数就不会重新计算,而是直接从缓存中获取对应的输出。
LRU 是 “Least Recently Used” 的缩写,意思是 “最近最少使用”。LRU 缓存就是一种缓存淘汰算法,当缓存达到预设的容量上限时,会优先淘汰最近最少使用的数据。
from functools import lru_cache @lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2) print(fib(10)) # 输出:55
在上面的例子中,我们定义了一个求斐波那契数列的函数,并且使用 @lru_cache(maxsize=None)
装饰器对其进行了装饰。然后我们调用 fib(10)
,得到结果 55。实际上,由于使用了缓存,fib
函数在求解过程中,对于同样的参数只进行了一次计算。
二、如何使用 functools.lru_cache?
要使用 functools.lru_cache
装饰器,你只需要在你的函数定义之前添加 @functools.lru_cache
行。这会让 lru_cache
装饰器知道你希望为这个函数添加一个缓存系统。
lru_cache
装饰器有两个可选参数:
maxsize
:这个参数用来设置缓存的大小。如果你设置了这个参数,缓存的大小就会被限制在这个值之内。如果你不设置这个参数,或者将其设置为None
,那么缓存的大小就没有上限。typed
:如果你将这个参数设置为True
,那么lru_cache
就会根据输入参数的类型分别进行缓存。也就是说,1
和1.0
尽管在 Python 中是相等的,但它们会被当成两个不同的输入进行缓存。默认情况下,typed
参数是False
。
from functools import lru_cache @lru_cache(maxsize=128, typed=False) def add(x, y): print(f"Calculating: {x} + {y}") return x + y print(add(1, 2)) # 输出:Calculating: 1 + 2 \n 3 print(add(1, 2)) # 输出:3 print(add(1.0, 2.0)) # 输出:Calculating: 1.0 + 2.0 \n 3.0 print(add(1.0, 2.0)) # 输出:3.0
在上面的代码中,我们定义了一个加法函数 add
,并使用 lru_cache
装饰器对其进行装饰。我们可以看到,当我们第二次调用 add(1, 2)
和 add(1.0, 2.0)
时,add
函数并没有重新进行计算,而是直接从缓存中获取了结果。
三、functools.lru_cache 的用途
functools.lru_cache
可以用于优化那些具有重复计算的递归函数,或者计算成本较高的函数。通过保存已经计算过的值,functools.lru_cache
能够避免重复的计算,从而提高程序的运行效率。
例如,求解斐波那契数列就是一个典型的使用场景。在没有优化的情况下,求解斐波那契数列的时间复杂度是指数级别的。但是,如果我们使用 functools.lru_cache
对其进行优化,那么我们就可以将其时间复杂度降低到线性级别。
此外,functools.lru_cache
还可以用于缓存那些对数据库或者文件系统的重复查询,从而提高程序的性能。
需要注意的是,functools.lru_cache
并不适合所有的场景。因为 functools.lru_cache
是通过空间换取时间的方式来提高程序的性能的,所以,如果你的程序运行在内存有限的环境中,或者你的函数有大量的不同输入,那么使用 functools.lru_cache
可能会导致内存消耗过大。此外,如果你的函数有副作用,或者依赖于外部状态,那么 functools.lru_cache
也可能无法正确地工作。在这些情况下,你可能需要寻找其他的优化策略。
总的来说,functools.lru_cache
是一个非常强大的工具,它能够帮助我们优化代码,提高程序的性能。当你在编写一个计算密集型或者需要大量重复计算的函数时,不妨考虑使用 functools.lru_cache
对其进行优化。
四、深入理解 functools.lru_cache
当我们将 functools.lru_cache
应用到函数上时,每次调用函数,它都会检查其参数是否已经在缓存中。如果在缓存中,它将返回缓存的结果,而不需要重新计算。如果没有在缓存中,那么函数将被调用并且结果将被添加到缓存中。当缓存满了,最少使用的条目将被抛弃。
以下是一个理解 functools.lru_cache
工作方式的例子:
from functools import lru_cache @lru_cache(maxsize=3) def foo(n): print(f"Running foo({n})") return n print(foo(1)) # 输出:Running foo(1) \n 1 print(foo(2)) # 输出:Running foo(2) \n 2 print(foo(3)) # 输出:Running foo(3) \n 3 print(foo(1)) # 输出:1 print(foo(2)) # 输出:2 print(foo(3)) # 输出:3 print(foo(4)) # 输出:Running foo(4) \n 4 print(foo(1)) # 输出:Running foo(1) \n 1
在这个例子中,我们设定 maxsize=3
,也就是只缓存最近的三个结果。当我们连续调用 foo(1)
,foo(2)
,foo(3)
时,这三个结果都被缓存了下来。再次调用这三个函数时,由于结果已经在缓存中,函数并没有被重新执行。但是当我们调用 foo(4)
时,由于缓存已满,所以最早被缓存的 foo(1)
的结果被移除了。再次调用 foo(1)
时,函数需要被重新执行。
这个例子说明了 functools.lru_cache
的 LRU 特性:当缓存达到上限时,最近最少使用的缓存会被移除。
五、清理和查看缓存
functools.lru_cache
还提供了两个方法用于清理和查看缓存:cache_clear
和 cache_info
。
cache_clear
方法可以清空所有的缓存。例如,在上面的 foo
函数中,我们可以通过 foo.cache_clear()
来清空所有的缓存。
cache_info
方法返回一个命名元组,描述了缓存的状态。它包含以下几个字段:hits
、misses
、maxsize
和 currsize
。其中,hits
和 misses
分别表示缓存命中和未命中的次数,maxsize
表示缓存的最大容量,currsize
表示当前缓存的使用量。
from functools import lru_cache @lru_cache(maxsize=3) def foo(n): print(f"Running foo({n})") return n foo(1) foo(2) foo(3) foo(4) print(foo.cache_info()) # 输出:CacheInfo(hits=0, misses=4, maxsize=3, currsize=3) foo(4) print(foo.cache_info()) # 输出:CacheInfo(hits=1, misses=4, maxsize=3, currsize=3) foo.cache_clear() print(foo.cache_info()) # 输出:CacheInfo(hits=0, misses=0, maxsize=3, currsize=0)
在这个例子中,我们首先调用了 foo(1)
,foo(2)
,foo(3)
和 foo(4)
。此时,由于 foo(1)
的缓存已经被淘汰,缓存中仅保留了 foo(2)
,foo(3)
和 foo(4)
的结果。调用 foo.cache_info()
,我们可以看到缓存未命中的次数为 4,当前缓存的使用量为 3。
然后我们再次调用 foo(4)
,由于这个结果已经在缓存中,所以这次是缓存命中,调用 foo.cache_info()
,我们可以看到缓存命中的次数变成了 1。
最后,我们调用 foo.cache_clear()
清空了所有的缓存,再次调用 foo.cache_info()
,我们可以看到当前缓存的使用量变成了 0。
以上,我们介绍了 functools.lru_cache
装饰器的使用方法和原理,包括如何使用 lru_cache
对函数进行优化,以及如何清理和查看缓存。希望这篇文章能够帮助你更好地理解和使用 functools.lru_cache
。
到此这篇关于一文带你深入理解Python的`functools.lru_cache`装饰器的文章就介绍到这了,更多相关Python `functools.lru_cache`装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!