知行编程网知行编程网  2022-12-03 17:00 知行编程网 隐藏边栏  16 
文章评分 0 次,平均分 0.0
导语: 本文主要介绍了关于神秘而强大的Python生成器精讲的相关知识,包括神秘boss,以及吴亦凡化学剦割这些编程知识,希望对大家有参考作用。

神秘而强大的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) 当trigger是next时(包括__next__方法,下同),yield的返回值(nRet记录的值)为None,如果是send,则此值为参数中的send值发送方法;

c) 调用生成器函数时,只生成一个生成器实例,并不实际执行。真正执行只有在第一次被next触发时才会进入函数执行。注意第一个trigger不能被send触发。

2) 调用生成器代码示意

def main():

初始化

f= fun()
next(f)

循环:

其他循环代码

nRet=send(x)

其他循环代码

上面代码示意表示:调用方执行自身初始化,然后进行生成器函数的初始化,然后执行循环迭代访问生成器函数的数据。

同样有几个细节老猿在此说明一下:

a) f= fun(),这条语句不会进入函数执行,只是生成一个generator实例f

b) 只有在循环代码中使用发送时才需要第一次下一次调用。如果循环中使用了next,则不需要执行一次send;

c) 当执行到第一个next时,会触发生成器函数被调用,从生成器中的第一行代码开始;后续的next或send执行将不再执行生成器函数的初始化部分,而是只从yield的第二部分开始执行,第二部分执行时,应该是在生成器函数的循环迭代代码中,所以之后也就是说,执行仍然会在生成器函数的循环代码中循环,直到遇到 yield 语句,并执行 yield 语句的第一部分。逻辑暂停功能并等待再次启动;

d) nRet记录的返回值是生成器函数yield后返回给trigger的数据。

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

你是否准备好结束存取款周期(是或是,否则继续该周期):n

mainf loop start call send …
PutPackage:After Yield, nRet= 22204
PutPackage:Before Yield…
mainf loop after call send ,nRet= PutPackage22204

你是否准备好结束存取款周期(是或是,否则继续该周期):是

PutPackage:After Yield, nRet= -1

上述代码中为什么要捕获异常?这是因为最后一个send(-1)时,是从yield第二部分执行,执行到循环“if nRet<1 : break”语句就会终止循环,不会再通过yield向触发方返回值,此时send执行就会出现迭代结束的异常。

3、 生成器函数的其他说明

Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这有利于节省内存,特别是生成器进行科学计算时很有用;

生成器是迭代器。除了next方法,还可以通过for循环遍历generator中的内容;

生成器除了前面介绍的__next__、send方法外,还有throw、close方法:

a) throw(type[, value[, traceback]]):这个方法在生成器暂停的地方抛出一个type类型的异常,并返回这个生成器函数产生的下一个值。如果生成器在没有产生下一个值的情况下退出,将引发 StopIteration 异常。如果生成器函数没有捕获传入的异常,或者引发了另一个异常,则该异常将传播给调用者。该方法可以解决上述case捕获异常的处理

b) close():在生成器函数暂停的地方引发 GeneratorExit。如果生成器函数然后正常退出、关闭或引发 GeneratorExit(因为未捕获到异常),则关闭并返回给其调用者。如果生成器产生了值,则关闭会引发 RuntimeError。如果生成器引发任何其他异常,它会传播给调用者。如果生成器已经由于异常或正常退出,则 close() 不执行任何操作。生成器可以直接由触发方调用close方法关闭,而不用像上面的情况那样在生成器函数中通过send判断发送的数据退出。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

知行编程网
知行编程网 关注:1    粉丝:1
这个人很懒,什么都没写
扫一扫二维码分享