python生成式与生成器,python代码生成器_1

  python生成式与生成器,python代码生成器

  一、 生成器(generator)概念

  生成器是一个特殊的迭代器,它保存算法。每次调用next()或send()时,都会计算下一个元素的值,直到计算完最后一个元素。当没有更多的元素时,抛出StopIteration。生成器有两种,一种是生成器表达式(也叫生成器求导),一种是生成器函数。

  二、 生成器表达式

  生成器表达式通过Python表达式语句计算一系列数据,但定义生成器时,并不生成数据,而是返回一个对象。该对象仅在需要时根据表达式计算要返回的当前数据:

  生成器来自迭代和列表解析的结合(在后面的列表解析章节中描述)。生成器类似于列表解析,但是它使用圆括号而不是方括号。生成器返回一个按需产生结果的对象,而不是一次构建一个结果列表;

  生成器的语法如下:

  (exprforiter _ variniterable)

  (EXPRITOR _ VARINTERABLEIFCOND _ EXPR)其中:

  Expr是计算generator元素值的表达式。

  对于iterable iter_var中的iter_var:表示对iterable对象iter_var中的每个元素进行表达式运算。

  If cond_exp:表示iterable对象中的元素需要满足指定的条件才能参与表达式运算。

  解释

  当在现有的一对括号中直接使用生成器表达式时(比如在函数调用中),不需要添加另一对括号。例如:sum(I * * 2 for I in range(10));

  生成器表达式的语法与列表解析的语法非常相似。因为涉及到一些相关的函数,所以老猿会回过头在列表解析相关章节介绍生成器表达式的内容。

  三、 生成器函数

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

  1.生成器函数和调用者的执行过程分析。

  生成器定义的示意图代码(非可执行代码)如下:

  Deffun():初始化

  循环:

  计算k

  NRet=yieldk其他循环代码

  上面的代码示意性的展示了:当生成器函数运行时,计算的结果K通过yield返回给调用者。K返回给调用者后,生成器函数停止执行,yield的调用执行结果不返回给生成器函数,nRet的赋值也不执行。等待下一次调用,然后返回yield本身的执行结果,继续后续循环代码,直到yield再次执行。

  老猿通过验证了解。以下是一些需要解释的细节:

  yield函数的执行是一个语句,但是该语句在实际执行时被分解为两部分。第一部分是将计算结果K返回到send或next的调用处(以下简称触发器),保存当前环境,暂停执行。另一部分是恢复当前环境,将yield本身的执行结果返回到生成器函数的调用处,继续执行后续循环。每次调用yield,除了第一次是从第一部分执行,后面的都是从第二部分执行。

  b)触发器为next(包括__next__ method,下同)时b)yield(nRet记录的值)的返回值为None,如果触发器为send,则为send方法的参数中的发送值;

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

  2)呼叫生成器代码示意图

  Main () 3360初始化

  f=乐趣()

  下一个(f)循环:

  其他循环代码

  NRet=发送(x)其他周期代码

  上面的代码示意性地展示了:调用者初始化自己,然后初始化生成器函数,然后迭代生成器函数。

  数的数据。

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

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

  b) 第一个next调用只有循环代码中使用send触发时才需要,如果循环中用next则无需先执行一次send;

  c) 第一个next执行时会触发调用生成器函数,从生成器第一行代码开始执行;后续的next或send执行,不再执行生成器函数的初始化部分,只是从yield的第二部分开始执行,第二部分执行时应该在生成器函数的循环迭代代码内,因此此后执行还是在生成器函数的循环代码内循环,直到遇到yield语句,执行完yield语句的第一部分逻辑挂起函数等待再次出发;

  d) nRet记录的返回值就是生成器函数yield后面返回给触发方的数据。

  2、 下面是一个老猿编写的模拟存快递包裹的生成器函数及其调用代码,每执行一次存包裹的函数就挂起,主程序等待确认是否继续循环,如果不继续则退出,代码如下:

  

importrandom

  defPutPackage():

  print(‘PutPackagestart…’)

  nRet=123

  whileTrue:

  ifnRet<1:break

  print(‘PutPackage:BeforeYield…’)

  nRet=yield’PutPackage’+str(nRet)#返回字符串PutPackage+上次循环yield的返回值

  print(‘PutPackage:AfterYield,nRet=’,nRet)

  ifnotnRet:continue

  defmainf():

  print(‘mainfstartcallPutPackage…’)

  vPutPackage=PutPackage()#只是返回生成器generator对象

  bBreak=False

  print(‘mainfstartcallnext…’)

  nRet=next(vPutPackage)#生成器初始化

  print(‘mainfendcallnext,nRet=’,nRet)

  whileTrue:

  ifbBreak:

  try:#为什么要捕获异常?

  vPutPackage.send(-1)#触发—1给生成器函数提示函数退出

  exceptStopIteration:pass

  break

  print(‘mainfloopstartcallsend…’)

  nRet=vPutPackage.send(random.randint(10000,99999))#产生一个随机包裹编号触发给生成器函数

  print(‘mainfloopaftercallsend,nRet=’,nRet)

  sConfirm=input(“是否准备结束存件取件循环(Y或y是,否则继续循环):”)

  ifsConfirm.strip().upper()==‘Y’:

  bBreak=True

  print("\n")

  mainf()

执行结果如下,大家对照前面的执行过程解析理解一下:

  

mainfstartcallPutPackage…

  mainfstartcallnext…

  PutPackagestart…

  PutPackage:BeforeYield…

  mainfendcallnext,nRet=PutPackage123

  mainfloopstartcallsend…

  PutPackage:AfterYield,nRet=66468

  PutPackage:BeforeYield…

  mainfloopaftercallsend,nRet=PutPackage66468

是否准备结束存件取件循环(Y或y是,否则继续循环):n

  

mainfloopstartcallsend…

  PutPackage:AfterYield,nRet=22204

  PutPackage:BeforeYield…

  mainfloopaftercallsend,nRet=PutPackage22204

是否准备结束存件取件循环(Y或y是,否则继续循环):y

  

PutPackage:AfterYield,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发送的数据来进行退出。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: