深入理解Python的`functools.lru_cache`装饰器

简介: 在 Python 中,有许多内置的装饰器可以用来增强函数或者类的功能。其中之一就是 `functools.lru_cache` 装饰器。这是一个非常有用的装饰器,它可以帮助我们优化递归函数,避免重复计算已经计算过的值。在这篇文章中,我们将探讨 `functools.lru_cache` 的工作原理以及如何使用它。

在 Python 中,有许多内置的装饰器可以用来增强函数或者类的功能。其中之一就是 functools.lru_cache 装饰器。这是一个非常有用的装饰器,它可以帮助我们优化递归函数,避免重复计算已经计算过的值。在这篇文章中,我们将探讨 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 就会根据输入参数的类型分别进行缓存。也就是说,11.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_clearcache_info

cache_clear 方法可以清空所有的缓存。例如,在上面的 foo 函数中,我们可以通过 foo.cache_clear() 来清空所有的缓存。

cache_info 方法返回一个命名元组,描述了缓存的状态。它包含以下几个字段:hitsmissesmaxsizecurrsize。其中,hitsmisses 分别表示缓存命中和未命中的次数,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

相关文章
|
7天前
|
开发者 Python
探索Python中的装饰器:从基础到高级应用
本文将带你深入了解Python中的装饰器,这一强大而灵活的工具。我们将一起探讨装饰器的基本概念,它们如何工作,以及如何使用它们来增强函数和类的功能,同时不改变其核心逻辑。通过具体代码示例,我们将展示装饰器的创建和使用,并探索一些高级应用,比如装饰器堆栈和装饰带参数的装饰器。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角,帮助你更有效地使用装饰器来简化和优化你的代码。
|
8天前
|
测试技术 数据安全/隐私保护 开发者
探索Python中的装饰器:从基础到高级应用
装饰器在Python中是一个强大且令人兴奋的功能,它允许开发者在不修改原有函数代码的前提下增加额外的功能。本文将通过具体代码示例,带领读者从装饰器的基础概念入手,逐步深入到高级用法,如带参数的装饰器和装饰器嵌套等。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
8天前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
20 6
|
10天前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
28 11
|
7天前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
装饰器,在Python中是一块强大的语法糖,它允许我们在不修改原函数代码的情况下增加额外的功能。本文将通过简单易懂的语言和实例,带你一步步了解装饰器的基本概念、使用方法以及如何自定义装饰器。我们还将探讨装饰器在实战中的应用,让你能够在实际编程中灵活运用这一技术。
23 7
|
6天前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
7天前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
29 6
|
6天前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
7天前
|
测试技术 开发者 Python
深入理解Python装饰器:从基础到高级应用
本文旨在为读者提供一个全面的Python装饰器指南,从其基本概念讲起,逐步深入探讨其高级应用。我们将通过实例解析装饰器的工作原理,并展示如何利用它们来增强函数功能、控制程序流程以及实现代码的模块化。无论你是Python初学者还是经验丰富的开发者,本文都将为你提供宝贵的见解和实用的技巧,帮助你更好地掌握这一强大的语言特性。
20 4
|
8天前
|
开发者 Python
Python中的装饰器:从入门到实践
本文将深入探讨Python的装饰器,这一强大工具允许开发者在不修改现有函数代码的情况下增加额外的功能。我们将通过实例学习如何创建和应用装饰器,并探索它们背后的原理和高级用法。
24 5