Python `lru_cache` 详解与最佳实践指南

目录

  • 核心特性
  • 使用方法
  • 1. 基本用法
  • 2. 参数说明
  • 缓存清理方法
  • 1. 清空整个缓存
  • 2. 手动淘汰旧缓存
  • 实用技巧
  • 1. 查看缓存状态
  • 2. 类型敏感缓存
  • 3. 缓存无参函数
  • 完整示例
  • 使用场景
  • LRU Cache 在 API 中的清理操作影响范围
  • 关键结论:
  • 示例演示:API 中的缓存隔离
  • 场景:两个 API 端点使用相同计算函数但不同缓存策略
  • 测试步骤及结果:
  • 重要注意事项:
  • 最佳实践建议:

  • lru_cache 是 Python 标准库
    functools 模块提供的装饰器,用于实现
    LRU(Least Recently Used)缓存策略。它会自动缓存函数的计算结果,当使用相同的参数再次调用时直接返回缓存结果,避免重复计算。


    核心特性

    1. LRU 策略:当缓存达到容量上限时,自动淘汰 最久未使用 的结果
    2. 线程安全:适合多线程环境
    3. 性能提升:特别适用于计算密集型函数

    使用方法

    1. 基本用法
    from functools import lru_cache
    
    @lru_cache(maxsize=128)  # 设置缓存容量
    def factorial(n):
        print(f"计算 {n} 的阶乘")
        return 1 if n <= 1 else n * factorial(n-1)
    
    print(factorial(5))  # 首次计算,会递归调用
    print(factorial(5))  # 直接返回缓存结果
    

    输出

    计算 5 的阶乘
    计算 4 的阶乘
    计算 3 的阶乘
    计算 2 的阶乘
    计算 1 的阶乘
    120
    120  # 无计算过程输出
    
    2. 参数说明
    @lru_cache(maxsize=None, typed=False)
    
  • maxsize:缓存容量(默认128)
  • None:无限缓存(慎用)
  • 0:禁用缓存
  • typed:是否区分参数类型(默认False
  • True1(int)和 1.0(float)视为不同参数

  • 缓存清理方法

    1. 清空整个缓存
    factorial.cache_clear()  # 清空所有缓存
    
    2. 手动淘汰旧缓存

    通过“伪调用”触发 LRU 淘汰:

    @lru_cache(maxsize=3)
    def square(x):
        return x * x
    
    square(1)  # 缓存 [1]
    square(2)  # 缓存 [1, 2]
    square(3)  # 缓存 [1, 2, 3]
    square(4)  # 淘汰最旧的1 → 缓存 [2, 3, 4]
    

    实用技巧

    1. 查看缓存状态
    print(square.cache_info())
    

    输出示例

    CacheInfo(hits=3, misses=5, maxsize=3, currsize=3)
    
  • hits:缓存命中次数
  • misses:缓存未命中次数
  • currsize:当前缓存数量
  • 2. 类型敏感缓存
    @lru_cache(typed=True)
    def type_sensitive(x):
        return type(x)
    
    print(type_sensitive(1))    # <class 'int'>
    print(type_sensitive(1.0))  # <class 'float'> (视为不同调用)
    
    3. 缓存无参函数
    @lru_cache()
    def get_config():
        return load_from_database()  # 只执行一次
    

    完整示例

    from functools import lru_cache
    import time
    
    @lru_cache(maxsize=3)
    def heavy_calculation(n):
        print(f"执行耗时计算: {n}")
        time.sleep(1)
        return n ** 2
    
    # 首次调用
    print(heavy_calculation(2))  # 执行计算
    print(heavy_calculation(3))  # 执行计算
    print(heavy_calculation(2))  # 使用缓存
    
    # 触发缓存淘汰
    print(heavy_calculation(4))  # 执行计算 → 缓存[2,3,4]
    print(heavy_calculation(5))  # 执行计算 → 淘汰2 → 缓存[3,4,5]
    
    # 查看缓存状态
    print(heavy_calculation.cache_info())
    # 输出: CacheInfo(hits=1, misses=4, maxsize=3, currsize=3)
    
    # 清空缓存
    heavy_calculation.cache_clear()
    print(heavy_calculation.cache_info())
    # 输出: CacheInfo(hits=0, misses=0, maxsize=3, currsize=0)
    

    使用场景

    1. 递归函数优化(如斐波那契数列)
    2. 数据转换/解析函数
    3. 配置加载等IO操作
    4. 计算成本高的纯函数

    注意:不适合用于:

  • 非确定性函数(如随机数生成)
  • 有副作用的函数
  • 参数不可哈希的函数(如列表、字典)
  • LRU Cache 在 API 中的清理操作影响范围

    在 Python 的 lru_cache 中,缓存是函数级别的,清理操作只会影响调用它的特定函数实例,不会影响其他函数或模块的缓存。

    关键结论:
    1. 每个函数有独立缓存:不同函数的缓存相互隔离
    2. 清理操作只影响当前函数:调用 func.cache_clear() 只清理该函数的缓存
    3. 同函数不同实例不共享缓存:相同函数的不同装饰器实例有独立缓存

    示例演示:API 中的缓存隔离
    场景:两个 API 端点使用相同计算函数但不同缓存策略
    from functools import lru_cache
    from fastapi import FastAPI
    
    app = FastAPI()
    
    # 端点1:使用小型缓存
    @lru_cache(maxsize=2)
    def calculate_small(n: int):
        print(f"小型缓存计算: {n}")
        return n * n
    
    # 端点2:使用大型缓存
    @lru_cache(maxsize=10)
    def calculate_large(n: int):
        print(f"大型缓存计算: {n}")
        return n * n
    
    @app.get("/small/{n}")
    async def small_endpoint(n: int):
        return {"result": calculate_small(n)}
    
    @app.get("/large/{n}")
    async def large_endpoint(n: int):
        return {"result": calculate_large(n)}
    
    @app.get("/clear-small")
    async def clear_small_cache():
        calculate_small.cache_clear()
        return {"message": "小型缓存已清空"}
    
    @app.get("/clear-large")
    async def clear_large_cache():
        calculate_large.cache_clear()
        return {"message": "大型缓存已清空"}
    

    测试步骤及结果:
    1. 首次调用小型端点
      GET /small/3 → 输出 “小型缓存计算: 3”

    2. 再次调用相同参数
      GET /small/3无计算输出(命中缓存)

    3. 调用大型端点相同参数
      GET /large/3 → 输出 “大型缓存计算: 3”
      (证明两个函数缓存独立)

    4. 清理小型缓存
      GET /clear-small → 返回清空消息

    5. 再次调用小型端点
      GET /small/3 → 重新输出 “小型缓存计算: 3”(缓存失效)

    6. 大型端点不受影响
      GET /large/3无计算输出(缓存仍然有效)


    重要注意事项:
    1. 多进程环境
      在 Gunicorn/Uvicorn 等多进程部署中,每个工作进程有独立缓存
      → 清理操作只影响当前工作进程的缓存

    2. 解决方案

      # 广播清理信号给所有进程(示例)
      @app.get("/clear-all")
      async def clear_all():
          # 通过消息队列或共享存储通知所有进程
          broadcast_clear_signal()
          return {"message": "已发送全局清理指令"}
      
    3. 类方法缓存
      类中的不同实例共享同一个缓存(除非使用实例方法)

      class Calculator:
          @classmethod
          @lru_cache
          def compute(cls, n):  # 所有实例共享缓存
              return n * n
      
    4. 模块级缓存
      同一模块内的多次装饰会创建不同缓存:

      # module_a.py
      @lru_cache
      def func(): ...  # 缓存A
      
      # module_b.py
      from module_a import func
      @lru_cache
      def wrapper():   # 缓存B(与func的缓存无关)
          return func()
      

    最佳实践建议:
    1. 按需清理:只清理需要更新的函数缓存
    2. 添加清理端点:为关键缓存函数提供专用清理API
    3. 监控缓存:定期检查 cache_info() 防止内存泄漏
    4. 设置合理大小:避免 maxsize=None 导致无限增长
    5. 跨进程协调:在分布式系统中使用 Redis 等集中式缓存替代

    作者:翔云123456

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python `lru_cache` 详解与最佳实践指南

    发表回复