学会 Python 缓存装饰器让函数性能翻倍

本文介绍了如何利用Python的funtools模块中的cache装饰器和cached_property特性提高代码性能,以及LRU缓存如何在函数调用时节省时间和资源。重点讲解了缓存策略及其适用场景。
摘要由CSDN通过智能技术生成

Python 是一种通用编程语言,可用于各种类型的项目,尤其是机器学习、后端开发和数据分析。在提高性能的同时,编写简洁易懂的代码也至关重要,尤其是当使用 Python 这样的动态解释型编程语言时。

为了提高性能,我们可以使用一些 Python 内置模块。在本文中,你将学习到编写更少、更高效代码的 functools 技巧。

Functools 是一个内置的 Python 模块,用于高阶函数和可调用对象上的操作。

cache 装饰器

如果您想提高一个函数的性能,而该函数返回一个值,您需要多次调用相同的参数,那么使用缓存装饰器就非常实用了。

基本上,缓存装饰器会记住参数和返回值。如果使用缓存装饰器多次调用具有相同参数的函数,则无需运行函数本身,因为我们已经知道了基于参数的输出值。

from functools import cache
import time

@cache
def add(x, y):
    """Adds two numbers together."""

    print("running")
    time.sleep(2)
    return x + y

# First two calls
print("NOT CACHED", add(2, 5))
print("CACHED", add(2, 5), "\n")

# Last two calls
print("NOT CACHED", add(6, 5))
print("NOT CACHED", add(3, 5))

在前两次调用中,add 函数运行了一次而不是两次,因为在第一次运行时,cache 恢复了参数和返回值对,而且因为 add 函数的第二次调用与第一次调用的参数相同,所以第二次调用没有运行函数,直接返回值。

但在最后两次调用中,add 函数运行了两次,因为这两次调用的参数与前两次调用的参数不同。不过需要注意,如果需要在函数中使用随机值,请不要使用缓存装饰器。

cached_property

将一个类方法转换为特征属性,一次性计算该特征属性的值,然后将其缓存为实例生命周期内的普通属性。 类似于 property() 但增加了缓存功能。

cached_property 装饰器仅在执行查找且不存在同名属性时才会运行。 当运行时,cached_property 会写入同名的属性。 后续的属性读取和写入操作会优先于 cached_property 方法,其行为就像普通的属性一样。

对于在其他情况下实际不可变的高计算资源消耗的实例特征属性来说该函数非常有用。可以理解为它是缓存和属性装饰器的组合。如果你熟悉 C# 和 Java 等其他编程语言,那么属性就类似于 getters

class Numbers:
    def __init__(self, *numbers):
        self.numbers = numbers

    @cached_property
    def total(self):
        print("Calculating total...")
        return sum(self.numbers)

    @cached_property
    def average(self):
        print("Calculating avarage...")
        return self.total/len(self.numbers)

m = Numbers(100, 90, 95)

print(m.average)
print(m.average)
print(m.average)

我们在 totalaverage 方法中使用 print 函数来检查方法本身是否正在运行,正如你所看到的,它们只运行一次,因为我们已经知道了返回值。

LRU 缓存

一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或 I/O 函数的调用时间。该缓存是线程安全的,因此被包装的函数可在多线程中使用。 这意味着下层的数据结构将在并发更新期间保持一致性。

简单地说,我们提供了一个数字,这个数字就是你想要调用的最近调用次数的最大值。当我们使用递归时,lru_cache 可能会根据问题的不同而有所帮助。

@lru_cache(maxsize=2)
def fib(n):
    if n < 2:
        return n
    print(f"calculating fib {n}")
    return fib(n-1) + fib(n-2)

s = time.time()
[fib(x) for x in range(30)]

print(time.time()-s)

print(fib.cache_info())

请尝试使用和不使用 lru_cache 装饰器的代码示例。使用 lru_cache 装饰器时,运行时间约为 9 秒,但在我的机器上,使用 lru_cache 装饰器时,运行时间约为 0.07 秒。

一般来说,LRU 缓存只应在你需要重复使用先前计算的值时使用。 因此,缓存有附带影响的函数、每次调用都需要创建不同的可变对象的函数(如生成器和异步函数)或不纯的函数如 time()random() 等是没有意义的。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值