74、Python之函数式编程:深入理解惰性求值与生成器

news2024/11/12 19:14:18

引言

我们在过程式编程或者面向对象编程中(当然也不局限于这些),涉及到计算、数据的转换处理时,通常是执行到对应的语句或者表达式时,就会完成计算或者数据处理。大多数场景下,这样立即计算的方式是没有问题的,但是当涉及到大数据场景时,可能会存在性能上的问题。

本文介绍的函数式编程中的另一个关键特性:惰性求值,可以用来涉及到大数据的计算、处理场景。

本文的主要内容有:

1、什么是惰性求值

2、惰性求值的适用场景

3、基于生成器实现惰性求值

什么是惰性求值

所谓“惰性求值(Lazy Evaluation)”是一种计算的策略,即在真正需要时才进行计算,而不是立即计算。

在惰性求值中,表达式的计算被推迟,直到结果确实需要的时候才进行真正的计算。

与之相对的是“及早计算(Eager Evaluation)”,这种策略是我们之前已经广泛使用到的,执行到表达式时,就会立即完成计算,并获取结果。

在不同的教材中,有时也会用另一种表述方式:“严格求值”与“非严格求值”。从字面意思就可以理解,所谓严格求值就是及早计算,所谓非严格求值就是惰性求值的意思。

需要说明的是,前面的其他特性更多的体现了函数式编程范式的简洁性、状态管理比较清晰、易于调试、测试等。而函数式编程之所以高效,原因之一就在于这种惰性求值的特性。

其实,Python或者其他语言中,经常用到的“短路运算”,也可以算作是惰性求值的一种形式。

比如:

age = 19
age >= 18 and print('成年人可以进网吧')
print('=' * 20)
age = 12
# 短路,后两行的print()均不会执行
age >= 18 and print('成年人可以进网吧')
age < 18 or print('未成年人等几年再来吧,如果网吧还在的话')

上面代码中,只有第一个print()会被执行,最后两个print()都不会被执行,由于短路运算的操作,没有执行的必要了。

在Python中,运算符and、or和if-else等都是非严格的,因为它们不需要计算全部参数就能提前得到整个表达式的最终结果了。

在其他语言中,也有同样的短路运算的实现。

惰性求值的适用场景

惰性求值的出发点在于进行性能优化,提升效率。所以,只要涉及到有性能优化的需要,都可以试着考虑能否通过惰性求值来实现。

惰性求值的典型场景主要有:

1、处理大数据集:当需要处理无法一次性装入内存的大数据集时,比如较大的文件、大型数据库的查询结果等,都可以使用惰性求值来优化内存的使用。

2、生成无限序列:如果需要一个无限的序列或者序列比较大,很可能导致内存溢出,这时,也可以考虑使用惰性求值来实现。

3、延迟计算以提高性能:当某些计算开销较大时,但是,并不总是需要用到其计算结果,那么就可以使用惰性求值,从而避免不必要的计算,从而提高性能。

4、流处理(Stream Processing):在大数据计算中,除了离线的批量计算外,还有一类场景的使用越来越频繁,就是实时大数据处理,也可以理解为实时流式数据处理,数据像流水一样,是持续的流转的,比如用户行为日志、传感器的监测数据等。这些场景也适合惰性求值。

基于生成器实现惰性求值

在Python中,要实现惰性求值,除了前面提到的“短路运算”外,其实更常用的是使用“生成器”。

生成器(Generator)是Python中一种特殊的迭代器,允许我们以惰性(lazy)的方式来生成序列的元素。

生成器主要是使用yield关键字来返回值,而不是像普通函数那样使用return返回。生成器函数在每次被调用时会暂停,并在下次调用时继续执行当前的位置,从而节省内存和提高效率。

Python中有两种方式来使用生成器:

1、使用生成器表达式来获得生成器,类似于列表推导式,只需要把[]换为(),既可以快速得到一个生成器。

2、通过定义函数的方式定义一个生成器,主要是使用yield关键字。

生成器表达式

首先来看生成器表达式的使用,直接看代码:

nums_list = [x * x for x in range(10)]
nums_generator = (x * x for x in range(10))
print(type(nums_list))
print(nums_list)
print('=' * 20)
print(type(nums_generator))
print(nums_generator)
print(list(nums_generator))
# 生成器可以使用for进行遍历
for num in nums_generator:
    print(num, end=' ')

执行结果:

df57b4c5ed3eec6bbcabb88a30890ed9.jpeg

从上述程序的执行可以得出:

1、列表推导式得到的是一个真实的列表对象,会直接占用对应的内存。

2、生成器表达式得到的是一个生成器对象,并不会直接分配同样大小的内存,只有在对生成器进行遍历时,才会真正计算每一个需要的元素。

3、两者在定义语法上的区别,只有[]与()的区别。

需要说明的是,列表可以进行多次遍历,但是,生成器只能进行一次遍历,所有的元素遍历完成了,不会从头来过。

此外,可以使用list(生成器对象)的方式将生成器对象转换为一个列表对象,本质上是对生成器对象进行遍历,用获得到的所有元素构建一个新的列表对象。当然构建之后,生成器对象本身如果使用for进行遍历,是获取不到任何元素的,因为已经被遍历一趟了。小数据集时,可以进行这种操作,数据量比较大时,需要注意性能。

使用yield定义生成器

直接看一个定义生成器函数的代码实例:

# 定义生成器函数
def square_num():
    for i in range(10):
        print(f'开始遍历,此时i={i}')
        yield i * i
        print(f'一次遍历完成')


print(type(square_num))
# 当进行函数调用时,则会获得生成器对象
generator = square_num()
print(type(generator))
print('=' * 20)
print(generator.__next__())
print('=' * 20)
print(next(generator))
print('=' * 30)
for num in generator:
    print(num)

执行结果:比较多,没有截全图

b8f510a42d762243f32c1c8c046569ed.jpeg

从这个生成器函数的定义及执行,可以得到如下结论:

1、在函数中使用了yield关键字,则该函数自动变为生成器函数,进行函数调用时,会获得一个生成器对象。

2、生成器对象是惰性求值的,每次手动调用next(生成器对象)或者调用生成器对象.__next__()方法,可以进行一次求值计算,返回内容为yield表达式后面的值。

3、每次求值计算,执行到yield表达式,就会阻塞,同时返回对应的值。下一次求值计算,从上次的阻塞点开始,继续执行。所以,能看到“一次遍历完成”出现在下一次求值计算的开头,而不是上一次求值计算的结尾。

此外,虽然代码演示中,没有涉及到,但是,还是有两点需要补充的:

1、生成器其实是一种特殊的迭代器,从定义中可以看到,生成器继承自迭代器

4a0f57e141b4196c5cab7632203d4d15.jpeg

2、当一个生成器已经完成所有元素的遍历时,继续手动进行next()函数的调用,则会抛出“StopIteration”异常。使用for循环进行遍历则不会抛异常。

前面的文章中,我们已经通过多种方式进行斐波那契数列的计算了,比如自定义装饰器缓存、内置装饰器、一行流等。接下来,我们以生成器的方式来实现斐波那契数列的计算。

直接看代码:

# 定义斐波那契生成器函数
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b


fib = fibonacci()
print(type(fib))
for i in range(10):
    print(next(fib), end=' ')

执行结果:

472ee01b3a8fd2269585ea7743b9f1fb.jpeg

以上,就是本文的全部内容了。

总结

本文主要介绍了惰性求值的概念及适用场景,然后重点介绍了Python中进行惰性求值的一种重要的方式——生成器,介绍了两种获得生成器的方法。

感谢您的拨冗阅读,希望对您有所帮助。

d1bd198898b6ea1010a2d23a96db7477.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2148502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的社团管理系统【附源码】

基于SpringBoot的社团管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 5.1.2 社长信…

【kafka-04】kafka线上问题以及高效原理

Kafka系列整体栏目 内容链接地址【一】afka安装和基本核心概念https://zhenghuisheng.blog.csdn.net/article/details/142213307【二】kafka集群搭建https://zhenghuisheng.blog.csdn.net/article/details/142253288【三】springboot整合kafka以及核心参数详解https://zhenghui…

Golang | Leetcode Golang题解之第416题分割等和子集

题目&#xff1a; 题解&#xff1a; func canPartition(nums []int) bool {n : len(nums)if n < 2 {return false}sum, max : 0, 0for _, v : range nums {sum vif v > max {max v}}if sum%2 ! 0 {return false}target : sum / 2if max > target {return false}dp …

修复 blender 中文输入 BUG (linux/wayland/GNOME/ibus)

blender 是一个很好的 开源 3D 建模/动画/渲染 软件, 功能很强大, 跨平台 (GNU/Linux, Windows 等系统都支持). 然而, 窝突然发现, blender 居然不支持中文输入 (linux) ! 这怎么能忍 ? 再一查, 不得了, 这居然是个 3 年前一直未解决的陈年老 BUG. 不行, 这绝对忍不了, 这个 …

关于单片机的技术原理及应用

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于单片机的技术原理及应用的相关内容&…

【Qt网络编程】Tcp多线程并发服务器和客户端通信

目录 一、编写思路 1、服务器 &#xff08;1&#xff09;总体思路widget.c&#xff08;主线程&#xff09; &#xff08;2&#xff09;详细流程widget.c&#xff08;主线程&#xff09; &#xff08;1&#xff09;总体思路chat_thread.c&#xff08;处理聊天逻辑线程&…

1、CycleGAN

1、CycleGAN CycleGAN论文链接&#xff1a;Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks CycleGAN 是一种流行的深度学习模型&#xff0c;用于图像到图像的转换任务&#xff0c;且不需要成对的数据。在介绍CycleGAN之前&#xff0c;必须…

Msf之Python分离免杀

Msf之Python分离免杀 ——XyLin. 成果展示&#xff1a; VT查杀率:8/73 (virustotal.com) 火绒和360可以过掉&#xff0c;但Windows Defender点开就寄掉了 提示&#xff1a;我用360测的时候&#xff0c;免杀过了&#xff0c;但360同时也申报了&#xff0c;估计要不了多久就寄…

《Linux运维总结:基于Ubuntu 22.04操作系统+x86_64架构CPU部署二进制mongodb 7.0.14分片集群》

总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:《Linux运维篇:Linux系统运维指南》 一、简介 1、应用场景 当您遇到如下问题时,可以使用分片集群解决: a、 存储容量受单机限制,即磁盘资源遭遇瓶颈。 b、 读写能力受单机限制,可能是CPU、内…

开关磁阻电机(SRM)系统的matlab性能仿真与分析

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 SRM的基本结构 4.2 SRM的电磁关系 4.3 SRM的输出力矩 5.完整工程文件 1.课题概述 开关磁阻电机(SRM)系统的matlab性能仿真与分析&#xff0c;对比平均转矩vs相电流&#xff0c;转矩脉动vs相电流&a…

Python OpenCV精讲系列 - 高级图像处理技术(九)

&#x1f496;&#x1f496;⚡️⚡️专栏&#xff1a;Python OpenCV精讲⚡️⚡️&#x1f496;&#x1f496; 本专栏聚焦于Python结合OpenCV库进行计算机视觉开发的专业教程。通过系统化的课程设计&#xff0c;从基础概念入手&#xff0c;逐步深入到图像处理、特征检测、物体识…

JavaWeb---纯小白笔记01:JavaWeb概述和Tomcat安装

本次将对WEB开发的相关的概念和Tomcat等进行介绍。 Web开发简介&#xff1a; C/S和B/S是两种常用的网络架构模式 区别&#xff1a; C/S&#xff1a;client/server --客户端与服务器之间直接进行通信,对用户&#xff0c;本地电脑要求高 B/S&#xff1a;browser/server--通过…

人工智能-大语言模型-微调技术-LoRA及背后原理简介

1. 《LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS》 LORA: 大型语言模型的低秩适应 摘要&#xff1a; 随着大规模预训练模型的发展&#xff0c;全参数微调变得越来越不可行。本文提出了一种名为LoRA&#xff08;低秩适应&#xff09;的方法&#xff0c;通过在Transf…

K8S容器实例Pod安装curl-vim-telnet工具

在没有域名的情况下&#xff0c;有时候需要调试接口等需要此工具 安装curl、telnet、vim等 直接使用 apk add curlapk add vimapk add tennet

Angular: ‘ng’ is not recognized as an internal or external command

背景 运行新项目的前端angular2项目时&#xff0c;需要全局安装angular-cli&#xff0c;然后使用ng serve --open命令启动项目。我安装好angular-cli后&#xff0c;在cmd里输入ng命令&#xff0c;死活无法识别。 解决过程 我按照网上的说法&#xff0c;去配置npm环境变量&am…

软考高级:数据库规范化: 1NF、2NF、3NF和 BCNF AI 解读

数据库的规范化是数据库设计中的一个重要过程&#xff0c;旨在减少数据冗余和提高数据一致性。它通过一系列规则&#xff08;称为范式&#xff09;来优化数据库表的结构。 常见的范式有1NF、2NF、3NF和BCNF。让我们分别来解释这些范式。 生活化例子 想象你在整理一个家庭成…

吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界

刚刚&#xff0c;阿里巴巴集团CEO、阿里云智能集团董事长兼CEO吴泳铭在2024云栖大会上发表主题演讲—— “ 过去22个月&#xff0c;AI发展速度超过任何历史时期&#xff0c;但我们依然还处于AGI变革的早期。生成式AI最大的想象力&#xff0c;绝不是在手机屏幕上做一两个新的超…

【论文阅读】Slim Fly: A Cost Effective Low-Diameter Network Topology 一种经济高效的小直径网络拓扑

文章目录 Slim Fly: A Cost Effective Low-Diameter Network Topology文章总结1. 摘要2. indroduction3. 主要工作 主要思想references Slim Fly: A Cost Effective Low-Diameter Network Topology Slim Fly&#xff1a;一种经济高效的小直径网络拓扑 SC’14 Maciej Besta 苏…

毕业设计选题:基于ssm+vue+uniapp的农产品自主供销小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

实战OpenCV之图像阈值处理

基础入门 图像阈值处理是一种二值化技术&#xff0c;它基于预设的阈值&#xff0c;可以将图像中的像素分为两大类&#xff1a;一大类是背景&#xff0c;另一大类是前景或目标对象。这个过程涉及将图像中的每个像素值与阈值进行比较&#xff0c;并根据比较结果决定保留原始值还是…