第4.2节 神秘而强大的Python生成器精讲
一、 生成器(generator)概念
生成器是一个特殊的迭代器,它保存的是算法,每次调用next()或send()就计算出下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration。生成器有两种类型,一种是生成器表达式(又称为生成器推导),一种是生成器函数。
二、 生成器表达式
生成器表达式是通过一个Python表达式语句去计算一系列数据,但生成器定义的时候数据并没有生成,而是返回一个对象,这个对象只有在需要的时候才根据表达式计算当前需要返回的数据:
- 生成器表达式来源于迭代和列表解析(列表解析后面章节介绍)的组合,生成器和列表解析类似,但是它使用小括号而不是中括号。生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表;
- 生成器表达式的语法如下:
(expr for iter_var in iterable)
(expr for iter_var in iterable if cond_expr)
其中:
expr为计算 生成器元素值的表达式
for iter_var in iterable iter_var:表示针对在可迭代对象iter_var中的每个元素进行表达式运算
if cond_exp:表示可迭代对象中的元素需要满足指定条件才会参与表达式运算 - 说明
- 直接在一对既有的小括号内(如在函数调用中)使用生成器表达式时,无需再添加一对小括号。例如:sum(i ** 2 for i in range(10));
- 生成器表达式与列表解析的语法非常象,由于涉及部分相关的函数,在列表解析相关的章节老猿再回头介绍一下生成器表达式有关的内容。
三、 生成器函数
生成器函数是一种语句中包含yield关键词的特殊的函数,它本身是一个迭代器,外部需要访问该迭代器数据的代码通过调用next函数(或迭代器的__next__方法)或send方法,触发函数执行计算并通过yield返回一个计算结果数据,返回数据后该函数立即停止执行,函数状态会保存在本地变量中,直到外部下次调用再激活,从上次停止执行部分开始执行。
1、 关于生成器函数与调用方的执行过程解析
- 生成器函数定义示意代码(非可执行代码)如下:
def fun():
初始化
循环:
计算得到k
nRet=yield k
其他循环代码
上面代码示意表示:生成器函数运行时计算得到结果k通过yield返回数据k给调用方,返回k给调用方之后,生成器函数停止执行,yield的调用执行结果并没有返回给生成器函数, nRet的赋值也并没有执行,等待下次调用后,再返回yield本身的执行结果,并继续后续循环代码,直到再次执行yield。
老猿通过验证理解有几个细节在此说明一下:
a) yield函数的执行是一条语句,但实际执行时该语句被分解成两部分,第一部分是将计算结果k返回给send或next调用处(下称触发方),保存当前环境,暂停执行,另一部分就是恢复当前环境,返回yield本身的执行结果给生成器函数的调用处,并继续往下执行后续循环。每次调用yield时,除了第一次是从第一部分执行,后续都是从第二部分开始执行。
b) yield返回值(nRet记下来的值)在触发方为next(含__next__方法,下同)时,为None,如果触发方是send,则该值为send方法参数中的发送值;
c) 生成器函数在调用时只是生成一个生成器实例,并没有真正执行,真正执行只有第一次通过next触发时才会进入函数执行,注意第一次触发不能是send方式触发。
2) 调用生成器代码示意
def main():
初始化
f= fun()
next(f)
循环:
其他循环代码
nRet=send(x)
其他循环代码
上面代码示意表示:调用方执行自身初始化,然后进行生成器函数的初始化,然后执行循环迭代访问生成器函数的数据。
同样有几个细节老猿在此说明一下:
a) f= fun(),这个语句不会进入函数执行,只是生成一个生成器实例f
b) 第一个next调用只有循环代码中使用send触发时才需要,如果循环中用next则无需先执行一次send;
c) 第一个next执行时会触发调用生成器函数,从生成器第一行代码开始执行;后续的next或send执行,不再执行生成器函数的初始化部分,只是从yield的第二部分开始执行,第二部分执行时应该在生成器函数的循环迭代代码内,因此此后执行还是在生成器函数的循环代码内循环,直到遇到yield语句,执行完yield语句的第一部分逻辑挂起函数等待再次出发;
d) nRet记录的返回值就是生成器函数yield后面返回给触发方的数据。
2、 下面是一个老猿编写的模拟存快递包裹的生成器函数及其调用代码,每执行一次存包裹的函数就挂起,主程序等待确认是否继续循环,如果不继续则退出,代码如下:
import random
def PutPackage():
print(‘PutPackage start…’)
nRet = 123
while True:
if nRet<1 : break
print(‘PutPackage:Before Yield…’)
nRet = yield ’ PutPackage’+str(nRet) #返回字符串PutPackage+上次循环yield的返回值
print(‘PutPackage:After Yield, nRet=’,nRet)
if not nRet: continue
def mainf():
print(‘mainf start call PutPackage …’)
vPutPackage=PutPackage() #只是返回生成器generator对象
bBreak = False
print(‘mainf start call next …’)
nRet=next(vPutPackage) #生成器初始化
print(‘mainf end call next,nRet=’,nRet)
while True:
if bBreak:
try: #为什么要捕获异常?
vPutPackage.send(-1) #触发—1给生成器函数提示函数退出
except StopIteration:pass
break
print(‘mainf loop start call send …’)
nRet=vPutPackage.send(random.randint(10000,99999)) #产生一个随机包裹编号触发给生成器函数
print(‘mainf loop after call send ,nRet=’,nRet)
sConfirm=input(“是否准备结束存件取件循环(Y或y是,否则继续循环):”)
if sConfirm.strip().upper()==‘Y’:
bBreak=True
print("\n")
mainf()
执行结果如下,大家对照前面的执行过程解析理解一下:
mainf start call PutPackage …
mainf start call next …
PutPackage start…
PutPackage:Before Yield…
mainf end call next,nRet= PutPackage123
mainf loop start call send …
PutPackage:After Yield, nRet= 66468
PutPackage:Before Yield…
mainf loop after call send ,nRet= PutPackage66468
是否准备结束存件取件循环(Y或y是,否则继续循环):n
mainf loop start call send …
PutPackage:After Yield, nRet= 22204
PutPackage:Before Yield…
mainf loop after call send ,nRet= PutPackage22204
是否准备结束存件取件循环(Y或y是,否则继续循环):y
PutPackage:After Yield, nRet= -1
上述代码中为什么要捕获异常?这是因为最后一个send(-1)时,是从yield第二部分执行,执行到循环“if nRet<1 : break”语句就会终止循环,不会再通过yield向触发方返回值,此时send执行就会出现迭代结束的异常。
3、 生成器函数的其他说明
- Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这有利于节省内存,特别是生成器进行科学计算时很有用;
- 生成器就是迭代器,除了next方法外,也可以通过for循环来遍历出生成器中的内容;
- 生成器除了前面介绍的__next__、send方法外,还有throw、close方法:
a) throw(type[, value[, traceback]]):该方法在生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。该方法可以解决上面案例捕获异常的处理
b) close():在生成器函数暂停的位置引发 GeneratorExit。 如果之后生成器函数正常退出、关闭或引发 GeneratorExit(由于未捕获该异常) 则关闭并返回其调用者。 如果生成器产生了一个值,关闭会引发 RuntimeError。 如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close() 不会做任何事。通过触发方调用close方法可以直接关闭生成器,而不需要象上面案例一样在生成器函数内判断send发送的数据来进行退出。
本节是在老猿结合手头资料并做了大量测试的基础上整理出来的生成器的知识,特别是关于生成器函数的执行过程详细谈了老猿的理解,老猿没有找到相关资料进行对照核实,只是从测试的代码执行路径分析,希望是正确的,否则就误导大家。其实最好的办法是找到yield的源码进行分析,可惜老猿暂时没有安排,希望以后有机会详细读一下源码确认一下。
老猿Python(https://blog.csdn.net/LaoYuanPython)系列文章用于逐步介绍老猿学习Python后总结的学习经验,这些经验有助于没有接触过Python的程序员可以很容易地进入Python的世界。
欢迎大家批评指正,谢谢大家关注!
第4.2节 神秘而强大的Python生成器精讲的更多相关文章
- 第三百六十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的基本查询
第三百六十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的基本查询 1.elasticsearch(搜索引擎)的查询 elasticsearch是功能 ...
- java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱
java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱 redis数据库 Redis企业集群高级应用精品教程[图灵学院] Redis权威指南 利用redis + lua解决抢红包高并 ...
- 第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门搜索
第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门 我的搜素简单实现原理我们可以用js来实现,首先用js获取到 ...
- 第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页
第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页 逻辑处理函数 计算搜索耗时 在开始搜索前:start_time ...
- 第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能
第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能 Django实现搜索功能 1.在Django配置搜索结果页的路由映 ...
- 第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中
第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中 前面我们讲到的elasticsearch( ...
- 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询
第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...
- 第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理
第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理 1.映射(mapping)介绍 映射:创建索引的时候,可以预先定义字 ...
- 第三百六十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)倒排索引
第三百六十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)倒排索引 倒排索引 倒排索引源于实际应用中需要根据属性的值来查找记录.这种索引表中的每一项都包 ...
随机推荐
- Luogu P3324 [SDOI2015]星际战争
二分+最大流 首先考虑二分答案 然后可以发现对于已知时间,判断是否可以将所有机器人摧毁可以用网络流 建立源点和汇点,源点向每一个激光武器连一条容量为$time*b[i]$的边,表示该激光武器在$tim ...
- 震惊!很多人都不知道 CSS Grid 框架早就有了!
前言 写作本文起源于知乎的一个问题:[CSS Grid 布局那么好,为什么至今没有人开发出基于 Grid 布局的前端框架呢?] 这篇文章拖沓了两个月,是因为真的不知道从哪里说好.这个问题的所有回答几乎 ...
- subString引起的index out of range
特别注意!!!低级坑 subString(begin,end) subList()均存在这个问题. 当end>String.size(),则index out of range!!!
- Databricks说的Lakehouse是什么?
在过去的几年里,Lakehouse作为一种新的数据管理范式,已独立出现在Databricks的许多用户和应用案例中.在这篇文章中,我们将阐述这种新范式以及它相对于之前方案的优势. 数据仓库在决策支持和 ...
- sk_buff结构--转载
套接字缓存之sk_buff结构 https://www.cnblogs.com/wanpengcoder/p/7529486.html 来此此处 sk_buff结构用来描述已接收或者待发送的数据报文信 ...
- 两种图片下拉放大效果实现(自定义CoordinatorLayout以及自定义Recylerview)
一.自定义CoordinatorLayout实现图片放大功能 本文是基于折叠布局实现的图片上拉滑动,下拉图片放大,松手放大的效果,先看下效果图. 实现原理: 1.使用CoordinatorLayout ...
- 最全Linux搭建SVN服务端教程
文章首推 支付宝接口对接 高德地图调用 验证码登录 QQ邮箱登录 今日主题:Linux搭建SVN服务端 简介 相信程序员对SVN还是不陌生的,虽然现在用Git比较多,但是SVN也是用的,SVN可以做代 ...
- deepin 20安装后系统没有声音解决方案(亲测有效)
打开终端: sudo vi /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT原有配置后面添加 snd_hda_intel.dmic_detect=0 即GRUB ...
- Git本地仓库和远程仓库冲突解决
场景描述: 在本地创建了一个git repo,并且执行了,git init命令,创建了.gitignore文件,或者README.md文件: 在远程创建了一个git repo,创建时也初始化了.git ...
- Java学习之Swing Gui编程
Java学习之Swing Gui编程 0x00 前言 前面的使用的Gui是基于Awt 去进行实现,但是在现实写Gui中 AWT实际运用会比较少. 0x01 Swing 概述 AWT 和Swing 区别 ...