Python中的缓存!

探索 Python 的 functools: 缓存、缓存属性和 LRU 缓存

Python 的 functools 模块是功能编程爱好者的宝藏,提供了使代码更高效和优雅的工具。在这篇文章中,我们将深入探讨三个强大的函数——cachecached_propertylru_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() 帮助您通过揭示缓存的有效性来调整性能。
  • 注意:避免与具有副作用的函数一起使用,因为缓存假设输出是确定性的。

更多