探索 Python 的 functools
: 缓存、缓存属性和 LRU 缓存
Python 的 functools
模块是功能编程爱好者的宝藏,提供了使代码更高效和优雅的工具。在这篇文章中,我们将深入探讨三个强大的函数——cache
、cached_property
和 lru_cache
——它们通过存储昂贵计算的结果来帮助优化性能。无论是加速递归算法还是简化基于类的计算,这些工具都能满足你的需求。让我们通过清晰的解释和实际的例子来探索每一个工具。
1. cache
: 简单的无界备忘录
cache
装饰器是一种轻量级的方式来备忘函数结果,当相同的输入再次出现时存储它们以供重用。它就像是你函数输出的便签——无需重新计算!
工作原理
- 功能:在一个无限制的字典中存储函数结果,使用参数作为键。
- 使用时机:适用于计算开销大的纯函数(相同输入,相同输出)。
- 主要特点:它等同于
lru_cache(maxsize=None)
,但由于其简单性而更快。
示例
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
print(factorial(10)) # 计算结果:3628800
print(factorial(10)) # 返回缓存结果,无需重新计算
为什么它很棒
- 速度:避免冗余计算,使得像阶乘这样的递归函数速度极快。
- 简单性:无需配置——只需添加装饰器即可。
- 警告:缓存会无限增长,因此对于具有许多唯一输入的函数,请监控内存使用情况。
2. cached_property
: 一次性属性计算
cached_property 装饰器将类方法转换为一个属性,该属性只计算一次其值并为实例缓存该值。可以将其视为一个懒加载的属性,它会一直存在。
工作原理
- 它的作用:第一次访问时运行该方法,将结果缓存为实例属性,并在后续调用中返回缓存的值。
- 何时使用:非常适合在第一次计算后不会改变的类中的昂贵计算。
关键特性:仅适用于实例方法(需要 self)。
示例
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print("计算面积")
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area) # 输出: 计算面积,然后 78.53975
print(c.area) # 输出: 78.53975(缓存,无需重新计算)
为什么它很棒
- 效率:每个实例仅计算一次,节省CPU周期。
- 简洁代码:像属性一样读取(c.area vs. c.area()),无缝融入类设计。
- 注意事项:缓存的值可以被覆盖(例如,c.area = 0),因此应将其用于不可变数据。
3. lru_cache
:灵活的、有界的记忆化
lru_cache装饰器是记忆化的强力工具,提供可配置大小的最近最少使用(LRU)缓存。它是线程安全的,并且具有丰富的反射特性,使其成为优化复杂函数的首选。
它是如何工作的
- 它的功能:缓存最多maxsize个结果,当缓存满时驱逐最近最少使用的条目。支持类型选项,将不同类型(例如,3与3.0)视为不同的键。
- 何时使用:非常适合递归算法、动态规划或任何具有重复调用的函数。
- 关键特性:提供 cache_info() 方法以检查命中、未命中和缓存大小。
示例
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(10)) # 计算结果:55
print(fib.cache_info()) # 显示: CacheInfo(hits=8, misses=11, maxsize=32, currsize=11)
为什么它很棒
- 控制:设置最大大小以平衡内存和性能(使用 None 表示无限制,如缓存)。
- 线程安全:适用于多线程应用程序,确保缓存保持一致性。
- 调试:cache_info() 帮助您通过揭示缓存的有效性来调整性能。
- 注意:避免与具有副作用的函数一起使用,因为缓存假设输出是确定性的。