程序员文章、书籍推荐和程序员创业信息与资源分享平台

网站首页 > 技术文章 正文

Python的lru_cache:提升函数性能的神奇魔法

hfteth 2025-06-10 15:15:55 技术文章 2 ℃

对话实录

小白:(苦恼)我写的一些函数计算特别耗时,每次调用都要重新计算,太浪费时间了,有没有办法优化呀?

专家:(神秘一笑)那你一定要了解lru_cache这个神奇的功能!它能像一个智能小助手,帮你的函数提升性能,接下来我给你好好讲讲。

lru_cache基础认识

1. 什么是lru_cache

lru_cache(Least Recently Used cache,最少使用缓存)是functools模块中的一个装饰器。简单来说,它会记住函数之前的计算结果。当函数再次被调用时,如果传入的参数和之前某次调用一样,它就直接返回之前缓存的结果,而不用重新计算,大大节省了时间。

2. 如何使用lru_cache

只需在函数定义前加上@lru_cache装饰器即可。例如,有一个计算阶乘的函数:

from functools import lru_cache
@lru_cache(maxsize = 128)
def factorial(n):
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

这里maxsize = 128表示缓存最多可以存储 128 个不同参数对应的结果。当缓存满了,它会把最近最少使用的结果清除掉,为新的结果腾出空间。

lru_cache常用场景

案例 1:递归函数-计算斐波那契数列

斐波那契数列由意大利数学家莱昂纳多斐波那契(Leonardo Fibonacci)在1202 年提出。它的定义是从第三项开始,每一项都等于前两项之和,即F(n)=F(n-1)+F(n-2)(n≥3,F(1)=1,F(2)=1

斐波那契数列的计算非常适合展示lru_cache的强大之处。因为在计算过程中,会有大量重复的子问题计算。

from functools import lru_cache
@lru_cache(maxsize = 128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
# 计算第30个斐波那契数
print(fibonacci(30))

接下里我们使用之前介绍的执行时间的模块timeit来对比使用lru_cache和不使用lru_cache时的执行时长。为了方便查看结果,并且保证计算结果的准确性,我们打印了结果值,供大家参考。

import timeit
#1 不使用lru_cache 计算n=20的执行时长  打印执行结果 方便我们确认每次执行结果是一致的
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

total_time = timeit.timeit(stmt='global result;result = fibonacci(20);print(f"运行结果:{result}")',setup='from __main__ import fibonacci',number=1000)
print(f"运行总耗时:{total_time:.6f}秒") #->1000次运行平均耗时

#2 使用lru_cache 计算n=20的执行时长
@lru_cache(maxsize = 128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
total_time = timeit.timeit(stmt='global result;result =fibonacci(20);print(f"运行结果:{result}")',setup='from __main__ import fibonacci',number=1000)
print(f"运行总耗时:{total_time:.6f}秒") #->1000次运行平均耗时

程序执行后 ,结果如下:

运行总耗时:1.662857秒
运行总耗时:0.001898秒

#可以看出使用lru_cache后,效率提升了930倍。

案例 2:复杂数学计算

假设有一个数学函数,比如计算一个多项式的值:

from functools import lru_cache

@lru_cache(maxsize = 128)
def complex_math(x):
    result = 3 * x ** 4 
    return result

for i in range(10):
    value = complex_math(i)
    print(f"x = {i}, result = {value}")

在实际应用中,一些复杂的数学计算可能会耗费大量资源。通过lru_cache,当重复计算时,直接返回缓存结果,提高了程序运行效率。

案例 3:文件读取与处理

有时候我们需要从文件中读取数据并进行处理,而文件读取操作相对耗时。如果每次读取相同文件并处理的结果可以复用,就可以使用lru_cache。

from functools import lru_cache

@lru_cache(maxsize = 10)
def read_and_process_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        data = file.read()
        word_count = len(data.split())
    return word_count

闭坑指南

缓存占用内存问题

lru_cache的缓存会占用内存空间。如果maxsize设置得过大,可能导致程序占用过多内存,影响系统性能。例如:

from functools import lru_cache

# 错误示范,maxsize设置过大
@lru_cache(maxsize = 10000)
def some_function(x):
    return x * x

# 假设这里频繁调用函数,会占用大量内存
for i in range(10000):
    some_function(i)

在设置maxsize时,要根据实际情况和系统内存状况合理调整,确保在提升性能的同时,不会对内存造成过大压力。

缓存失效问题

当被缓存函数的计算逻辑发生改变,或者函数依赖的外部资源(如文件内容)发生变化时,缓存可能不会自动更新,导致返回的是旧结果。例如:

from functools import lru_cache

@lru_cache(maxsize = 128)
def calculate_value(x):
    # 假设这里的计算逻辑改变了,但缓存未更新
    return x * 2

# 第一次调用,结果被缓存
result1 = calculate_value(5)
# 函数逻辑改变后,未清除缓存
# 第二次调用仍返回缓存的旧结果
result2 = calculate_value(5)

在修改函数逻辑或外部资源变化后,需要手动清除缓存。可以使用cache_clear方法,例如
calculate_value.cache_clear(),或者重新启动程序,以确保获取到最新的计算结果。

专家工具箱

1. 缓存命中率分析

通过cache_info方法可以获取lru_cache的缓存信息,包括命中次数、未命中次数等。这有助于分析缓存的使用情况,进而优化缓存设置。例如:

from functools import lru_cache

@lru_cache(maxsize = 128)
def complex_calculation(x):
    # 模拟复杂计算
    return x * x * x


# 多次调用函数
for i in range(10):
    complex_calculation(i)

for i in range(10):
    complex_calculation(i)

for i in range(10):
    complex_calculation(i)

cache_info = complex_calculation.cache_info()
print(cache_info)

#输出结果:
CacheInfo(hits=20, misses=10, maxsize=128, currsize=10)
#第1次执行for循环时 并没有命中,等第2次和第3次执行时都命中了

输出的cache_info包含hits(命中次数)、misses(未命中次数)、maxsize(最大缓存数量)和currsize(当前缓存数量)等信息。

2. 缓存类型设置

lru_cache还有一个typed参数,默认为False。当typed = True时,不同类型但值相同的参数会被视为不同的缓存键。例如:

from functools import lru_cache
#设置 typed = True的场景
@lru_cache(maxsize = 128, typed = True)
def add_numbers(a, b):
    return a + b

result1 = add_numbers(2, 3)
result2 = add_numbers(2.0, 3.0)
cache_info = add_numbers.cache_info()
print(cache_info)

# 不设置typed,默认为False
@lru_cache(maxsize = 128)
def add_numbers(a, b):
    return a + b

result1 = add_numbers(2, 3)
result2 = add_numbers(2.0, 3.0)
cache_info = add_numbers.cache_info()
print(cache_info)

#输出结果:
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)

在这个例子中,当typed默认为 False时,add_numbers(2, 3)和add_numbers(2.0, 3.0)会被视为相同的调用,因为2和2.0在数值上相等,所以显示命中了1次。但当typed = True时,它们会被当作不同的调用,分别缓存结果,显示并没有命中。

小白:(恍然大悟)哇,lru_cache原来这么厉害,能解决我这么多函数性能问题!

专家:(点头)没错,掌握好lru_cache的使用,能让你的Python程序运行得更加高效,快去实践一下吧!

lru_cache常用设置及方法速查表

设置 / 方法

用法

说明

maxsize

@lru_cache(maxsize = 128)

设置缓存的最大数量,当缓存满时,删除最近最少使用的缓存项

typed

@lru_cache(maxsize = 128, typed = False)

控制是否区分不同类型但值相同的参数,默认为False

cache_clear

function.cache_clear()

手动清除缓存

cache_info

function.cache_info()

获取缓存信息,包括命中次数、未命中次数等

Tags:

最近发表
标签列表