一、 生成器(generator)概念

生成器是一个特殊的迭代器,它保存的是算法,每次调用next()或send()就计算出下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration。生成器有两种类型,一种是生成器表达式(又称为生成器推导),一种是生成器函数。

二、 生成器表达式

生成器表达式是通过一个Python表达式语句去计算一系列数据,但生成器定义的时候数据并没有生成,而是返回一个对象,这个对象只有在需要的时候才根据表达式计算当前需要返回的数据:

  1. 生成器表达式来源于迭代和列表解析(列表解析后面章节介绍)的组合,生成器和列表解析类似,但是它使用小括号而不是中括号。生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表;
  2. 生成器表达式的语法如下:

    (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:表示可迭代对象中的元素需要满足指定条件才会参与表达式运算
  3. 说明
  1. 直接在一对既有的小括号内(如在函数调用中)使用生成器表达式时,无需再添加一对小括号。例如:sum(i ** 2 for i in range(10));
  2. 生成器表达式与列表解析的语法非常象,由于涉及部分相关的函数,在列表解析相关的章节老猿再回头介绍一下生成器表达式有关的内容。

三、 生成器函数

生成器函数是一种语句中包含yield关键词的特殊的函数,它本身是一个迭代器,外部需要访问该迭代器数据的代码通过调用next函数(或迭代器的__next__方法)或send方法,触发函数执行计算并通过yield返回一个计算结果数据,返回数据后该函数立即停止执行,函数状态会保存在本地变量中,直到外部下次调用再激活,从上次停止执行部分开始执行。

1、 关于生成器函数与调用方的执行过程解析

  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、 生成器函数的其他说明

  1. Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这有利于节省内存,特别是生成器进行科学计算时很有用;
  2. 生成器就是迭代器,除了next方法外,也可以通过for循环来遍历出生成器中的内容;
  3. 生成器除了前面介绍的__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生成器精讲的更多相关文章

  1. 第三百六十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的基本查询

    第三百六十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的基本查询 1.elasticsearch(搜索引擎)的查询 elasticsearch是功能 ...

  2. java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱

    java高级精讲之高并发抢红包~揭开Redis分布式集群与Lua神秘面纱 redis数据库 Redis企业集群高级应用精品教程[图灵学院] Redis权威指南 利用redis + lua解决抢红包高并 ...

  3. 第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门搜索

    第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门 我的搜素简单实现原理我们可以用js来实现,首先用js获取到 ...

  4. 第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页

    第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页 逻辑处理函数 计算搜索耗时 在开始搜索前:start_time ...

  5. 第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能

    第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能 Django实现搜索功能 1.在Django配置搜索结果页的路由映 ...

  6. 第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中

    第三百六十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)scrapy写入数据到elasticsearch中 前面我们讲到的elasticsearch( ...

  7. 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询

    第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...

  8. 第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理

    第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理 1.映射(mapping)介绍 映射:创建索引的时候,可以预先定义字 ...

  9. 第三百六十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)倒排索引

    第三百六十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)倒排索引 倒排索引 倒排索引源于实际应用中需要根据属性的值来查找记录.这种索引表中的每一项都包 ...

随机推荐

  1. Python中的小括号()、中括号[]、花括号{}区别

    Python中最常见括号的区别: 在Python语言中最常见的括号有三种,分别是:小括号().中括号[].花括号{}:其作用也不相同,分别用来代表不同的Python基本内置数据类型. Python中的 ...

  2. 剑指29:最小的k个数

    题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4. class Solution {public:    vector& ...

  3. 虚拟DOM与diff算法

    虚拟DOM与diff算法 虚拟DOM 在DOM操作中哪怕我们的数据,发生了一丢丢的变化,也会被强制重建整预DOM树.这么做,涉及到很多元素的重绘和重排,导致性能浪费严重 只要实现按需更新页面上的元素即 ...

  4. orphan sockets

    orphan sockets 介绍一下什么是 orphan sockets,简单来说就是该 socket 不与任何一个文件描述符相关联.例如,当应用调用 close() 关闭一个链接时,此时该 soc ...

  5. linux netfilter nat1

    linux netfilter nat1 2020整理云笔记上传

  6. sql sever 2008基础知识

    下面是一些总结,如果执行时发现错误,可以查看错误消息进行解决,也可上网查资料 数据库的组成: 主数据文件:有且只有一个,扩展名为.mdf. 次数据文件:可以没有,也可以有任意个.扩展名为.ndf. 日 ...

  7. ceph的jewel新支持的rbd-nbd

    jewel版本新增加了一个驱动NBD,允许librbd实现一个内核级别的rbd NBD相比较于kernel rbd: rbd-ko是根据内核主线走的,升级kernel rbd需要升级到相应的内核,改动 ...

  8. vue+node+mysql

    准备工作 安装node,这是必须的 新版node自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西.验证 ...

  9. 理解 ASP.NET Core: 处理管道

    理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...

  10. HDU100题简要题解(2020~2029)

    HDU2020 绝对值排序 题目链接 Problem Description 输入n(n<=100)个整数,按照绝对值从大到小排序后输出.题目保证对于每一个测试实例,所有的数的绝对值都不相等. ...