python生成器(转)
生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义__iter__()和next()方法。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。
一、yield和迭代器生成器
迭代器是非常高效的类型,无论是从时间复杂度,还是从空间复杂度。而实现迭代器的代码虽然简单,却也繁琐。为此,python定义了一个yield关键字,专门用来构造迭代器。yield有生成,产生的意思。
yield的功能和return非常类似,它们都只能在方法中使用。不同的是,包含yield语句的方法被称为生成器方法。当调用生成器方法时,会返回一个生成器对象。
例如,看下面的例子。
def MyGenerator():
yield 1 gen = MyGenerator()
print gen
输出结果为
<generator object MyGenerator at 0x0000000001D9DD80>
当调用生成器对象的next方法时,会执行生成器方法中的代码,直至遇到yield语句时,方法的执行过程会被挂起。同时,方法运行的上下文环境会被保存。而next方法的返回值就是yield关键字后面表达式的返回值。
例如,下面代码
print gen.next()
执行结果为
1
当我们继续调用next方法时,从上一次挂起的地方开始,继续执行后面的代码。直至遇到下一个yield语句。当方法执行完毕,依然没有遇到yield语句,抛出StopIteration异常。
例如
def MyGenerator():
yield 1
yield 'a' gen = MyGenerator()
print gen.next()
print gen.next()
print gen.next()
上面代码中第1次调用next方法,执行语句yield 1。第2次调用next方法,执行语句yield 'a'。第3次调用next方法时,在方法退出前都没有遇到yield语句,因此抛出StopIteration异常。
上面介绍的生成器方法的工作机理。在后面的博文中,会逐步介绍生成器方法的一些经典应用。
二、通过生成器函数构造序列对象的迭代器
事实上,一个序列对象的迭代器,依赖于一个整数序列的迭代器。看下面的代码
def MyGenerator(len):
start = 0
while start < len:
yield start
start = start + 1 gen = MyGenerator(3)
print gen.next()
print gen.next()
print gen.next()
print gen.next()
当调用第1次next方法时, 会首先执行MyGenerator方法的第1行代码start = 0。然后进入循环。这里len的值通过参数传入为3。因此while的条件表达式为真。进入循环后,遇到yield语句,方法的执行过程被挂起。next方法的返回值为start的值,即0。
当调用第2次next方法时,接着上面的挂起点,往下执行start = start + 1语句,start的值变为1。接着又进入while循环的条件判断,start<len依然为真。因此,又执行yield语句。但是由于start值为1,故而这一次next方法返回的值为1。
第3次next方法的调用类似。
当调用第4次next方法时,while循环的条件判断start < len为假,while循环结束,MyGenerator方法调用也随之结束,抛出StopIteration异常。
输出结果
0
1
2
Traceback (most recent call last):
File "test.py", line 21, in <module>
print gen.next()
StopIteration
有了上面的结果,重写序列对象的迭代器轻而易举。
def MyGenerator(sequence):
start = 0
while start < len(sequence):
yield sequence[start]
start = start + 1 gen = MyGenerator([1,2,3,'a','b','c']) for i in gen:
print i
对比之前迭代器类的代码,我们可以认识到,yield关键字为构造迭代器提供了多大的方便。它使得代码长度缩减许多,同时也大大增强了可读性。
三、生成器对象的send方法
生成器对象是一个迭代器。但是它比迭代器对象多了一些方法,它们包括send方法,throw方法和close方法。这些方法,主要是用于外部与生成器对象的交互。本文先介绍send方法。
send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值。这样说起来比较抽象,看下面的例子。
def MyGenerator():
value = (yield 1)
value = (yield value) gen = MyGenerator()
print gen.next()
print gen.send(2)
print gen.send(3)
输出的结果如下
1
2
Traceback (most recent call last):
File "test.py", line 18, in <module>
print gen.send(3)
StopIteration
上面代码的运行过程如下。
当调用gen.next()方法时,Python首先会执行MyGenerator方法的yield 1语句。由于是一个yield语句,因此方法的执行过程被挂起,而next方法返回值为yield关键字后面表达式的值,即为1。
当调用gen.send(2)方法时,python首先恢复MyGenerator方法的运行环境。同时,将表达式(yield 1)的返回值定义为send方法参数的值,即为2。这样,接下来value=(yield 1)这一赋值语句会将value的值置为2。继续运行会遇到yield value语句。因此,MyGenerator方法再次被挂起。同时,send方法的返回值为yield关键字后面表达式的值,也即value的值,为2。
当调用send(3)方法时MyGenerator方法的运行环境。同时,将表达式(yield value)的返回值定义为send方法参数的值,即为3。这样,接下来value=(yield value)这一赋值语句会将value的值置为3。继续运行,MyGenerator方法执行完毕,故而抛出StopIteration异常。
总的来说,send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互。但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错。例如
gen = MyGenerator()
print gen.send(2)
上面代码的输出为
Traceback (most recent call last):
File "test.py", line 16, in <module>
print gen.send(2)
TypeError: can't send non-None value to a just-started generator
当然,下面的代码是可以接受的
gen = MyGenerator()
print gen.send(None)
因为当send方法的参数为None时,它与next方法完全等价。但是注意,虽然上面的代码可以接受,但是不规范。所以,在调用send方法之前,还是先调用一次next方法为好。
四、生成器对象的throw方法
上边介绍的send方法,通过向生成器对象传递参数来实现与生成器对象的交互。本文介绍与生成器对象的另一种方式,即throw方法。它的实现手段是通过向生成器对象在上次被挂起处,抛出一个异常。之后会继续执行生成器对象中后面的语句,直至遇到下一个yield语句返回。如果在生成器对象方法执行完毕后,依然没有遇到yield语句,抛出StopIteration异常。
请看下面的例子
def myGenerator():
value = 1
while True:
yield value
value += 1 gen = myGenerator()
print gen.next()
print gen.next()
print gen.throw(Exception, "Method throw called!")
输出结果为
1
2
Traceback (most recent call last):
File "test.txt", line 11, in <module>
print gen.throw(Exception, "Method throw called!")
File "test.txt", line 4, in myGenerator
yield value
Exception: Method throw called!
代码的最后一句向生成器对象抛出了一个异常。但是,在生成器对象的方法时没有处理该异常的代码,因此异常会被抛出到主方法。
下面的示例中,添加了处理异常的代码
def myGenerator():
value = 1
while True:
try:
yield value
value += 1
except:
value = 1 gen = myGenerator()
print gen.next()
print gen.next()
print gen.throw(Exception, "Method throw called!")
在上面的代码中,加入了一个try-except语句块处理异常。当生成器方法收到异常后,会调到except语句块,将value置为1。因此,代码的输出如下。
1
2
1
Exception RuntimeError: 'generator ignored GeneratorExit' in <generator object myGenerator at 0x00000000028BB900> ignored
上面输出中,第2个1是gen.throw方法的返回值。在执行完该方法后,生成器对象方法的while循环并没有结束,也即是说生成器方法的执行还没有结束。这个时候如果强制结束主程序,会抛出一个RuntimeError。也就是上面输出的第4行。要优雅地关闭主程序,需要用到生成器对象的close方法。
五、GeneratorExit异常
当一个生成器对象被销毁时,会抛出一个GeneratorExit异常。请看下面的代码。
def myGenerator():
try:
yield 1
except GeneratorExit:
print "myGenerator exited" gen = myGenerator()
print gen.next()
输出结果为
1
myGenerator exited
上面代码的运行逻辑如下: 当调用到gen.next()方法时,会执行生成器对象方法的yield语句。此后,主程序结束,系统会自动产生一个GeneratorExit异常,被生成器对象方法的Except语句块截获。
而GeneratorExit异常产生的时机,是在生成器对象被销毁之前。为了验证这点,请看下面的代码。
def myGenerator():
try:
yield 1
yield 2
except GeneratorExit:
print "myGenerator exited" gen = myGenerator()
print gen.next()
del gen
print "Main caller exited"
输出结果
1
myGenerator exited
Main caller exited
值得一提的是,GeneratorExit异常只有在生成器对象被激活后,才有可能产生。更确切的说,需要至少调用一次生成器对象的next方法后,系统才会产生GeneratorExit异常。请看下面的代码。
def myGenerator():
try:
yield 1
yield 2
except GeneratorExit:
print "myGenerator exited" gen = myGenerator()
del gen
print "Main caller exited"
其输出结果如下:
Main caller exited
在上面的示例中,我们都显式地捕获了GeneratorExit异常。如果该异常没有被显式捕获,生成器对象也不会把该异常向主程序抛出。因为GeneratorExit异常定义的初衷,是方便开发者在生成器对象调用结束后定义一些收尾的工作,如释放资源等。
六、生成器对象的close方法
生成器对象的close方法会在生成器对象方法的挂起处抛出一个GeneratorExit异常。GeneratorExit异常产生后,系统会继续把生成器对象方法后续的代码执行完毕。参见下面的代码。
def myGenerator():
try:
yield 1
print "Statement after yield"
except GeneratorExit:
print "Generator error caught" print "End of myGenerator" gen = myGenerator()
print gen.next()
gen.close()
print "End of main caller"
代码执行过程如下:
- 当调用gen.next方法时,会激活生成器,直至遇到生成器方法的yield语句,返回值1。同时,生成器方法的执行被挂起。
- 当调用gen,close方法时,恢复生成器方法的执行过程。系统在yield语句处抛出GeneratorExit异常,执行过程跳到except语句块。当except语句块处理完毕后,系统会继续往下执行,直至生成器方法执行结束。
代码的输出如下:
1
Generator error caught
End of myGenerator
End of main caller
需要注意的是,GeneratorExit异常的产生意味着生成器对象的生命周期已经结束。因此,一旦产生了GeneratorExit异常,生成器方法后续执行的语句中,不能再有yield语句,否则会产生RuntimeError。请看下面的例子。
def myGenerator():
try:
yield 1
print "Statement after yield"
except GeneratorExit:
print "Generator error caught" yield 3 gen = myGenerator()
print gen.next()
gen.close()
print "End of main caller"
输出结果为
1
Generator error caught
Traceback (most recent call last):
File "test.txt", line 12, in <module>
gen.close()
RuntimeError: generator ignored GeneratorExit
注意,由于RuntimError会向主方法抛出,因此主方法最后的print语句没有执行。
有了上面的知识,我们就可以理解为什么下面的代码会抛出RuntimError错误了。
def myGenerator():
value = 1
while True:
try:
yield value
value += 1
except:
value = 1 gen = myGenerator()
print gen.next()
print gen.next()
print gen.throw(Exception, "Method throw called!")
上面代码中,当主程序结束前,系统产生GeneratorExit异常,被生成器对象方法的except语句捕获,但是此时while语句还没有退出,因此后面还会执行“yield value”这一语句,从而发生RuntimeError。要避免这个错误非常简单,请看下面的代码。
def myGenerator():
value = 1
while True:
try:
yield value
value += 1
except Exception:
value = 1 gen = myGenerator()
print gen.next()
print gen.next()
print gen.throw(Exception, "Method throw called!")
代码第7行的except语句声明只捕获Exception异常对象。这样,当系统产生GeneratorExit异常后,不再被except语句捕获,继续向外抛出,从而跳出了生成器对象方法的while语句。
这里再简单说一句,GeneratorExit异常继承自BaseException类。BaseException类与Exception类不同。一般情况下,BaseException类是所有内建异常类的基类,而Exception类是所有用户定义的异常类的基类。
转自:http://blog.csdn.net/hedan2013/article/details/72811117
python生成器(转)的更多相关文章
- python——生成器
python——生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个 ...
- Python生成器-博文读后感
Windows 10家庭中文版,Python 3.6.4, 上午看过了一篇讲Python生成器的博文: 提高你的Python: 解释‘yield’和‘Generators(生成器)’(英文原文) 这篇 ...
- 小学生都能学会的python(生成器)
小学生都能学会的python(生成器) 1. 生成器 生成器的本质就是迭代器. 生成器由生成器函数来创建或者通过生成器表达式来创建 # def func(): # lst = [] # for i i ...
- Python 生成器 (generator) & 迭代器 (iterator)
python 生成器 & 迭代器 生成器 (generator) 列表生成式 列表生成式用来生成一个列表,虽然写的是表达式,但是储存的是计算出来的结果,因此生成的列表受到内存大小的限制 示例: ...
- python生成器学习
python生成器学习: 案例分析一: def demo(): for i in range(4): yield i g=demo() g1=(i for i in g) #(i for i in d ...
- 【python之路29】python生成器generator与迭代器
一.python生成器 python生成器原理: 只要函数中存在yield,则函数就变为生成器函数 #!usr/bin/env python # -*- coding:utf-8 -*- def xr ...
- Generator - Python 生成器
Generator, python 生成器, 先熟悉一下儿相关定义, generator function 生成器函数, 生成器函数是一个在定义体中存有 'yield' 关键字的函数. 当生成器函数被 ...
- python生成器原理剖析
python生成器原理剖析 函数的调用满足"后进先出"的原则,也就是说,最后被调用的函数应该第一个返回,函数的递归调用就是一个经典的例子.显然,内存中以"后进先出&quo ...
- 什么是Python生成器?与迭代器的关系是什么?
生成器是一个特殊的迭代器,它保存的是算法,每次调用next()或send()就计算出下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration.生成器有两种类型,一种是生 ...
- Python 生成器与迭代器 yield 案例分析
前几天刚开始看 Python ,后因为项目突然到来,导致Python的学习搁置了几天.然后今天看回Python 发现 Yield 这个忽然想不起是干嘛用的了(所以,好记性不如烂笔头.).然后只能 花点 ...
随机推荐
- 【转】ORACLE Dataguard安装
ORACLE Dataguard安装 标签: oracledatabasearchivesql数据库list 2011-08-01 09:40 5548人阅读 评论(1) 收藏 举报 分类: ORA ...
- JS时间(日期)比较或相减(暂时停用)
注:此文均来自网上,可行,只供参考 //JAVASCRIPT中 日期相减很麻烦 ,现在有现成的实现方法,拷贝过去就可以用了,方便 //调用该方法(主方法) function dateDiff(date ...
- ios之block笔记
目测和函数指针基本类似用法,贴个hello world,备用 typedef int (^TestBlock)(int val1,int val2); __block ;//这里加__block是为了 ...
- JAVA面向对象编程课程设计——网络版单机斗地主
一.团队介绍 成员姓名 任务分配 成员课程设计博客链接 兰泽祥(组长) 数据库,斗地主规则的实现,人机自动出牌的算法,实体类的设计 JAVA面向对象编程课程设计--web版斗地主 吴修恩 JSP界面的 ...
- 线性代数之SVD与PCA
[作者:byeyear Email:east3@163.com 首发www.cnblogs.com 转载请注明] 回忆学校的美好时光,一起来复习下曾经的课程吧. 1. SVD推荐am ...
- 【Spring学习笔记-MVC-5】利用spring MVC框架,实现ajax异步请求以及json数据的返回
作者:ssslinppp 时间:2015年5月26日 15:32:51 1. 摘要 本文讲解如何利用spring MVC框架,实现ajax异步请求以及json数据的返回. Spring MV ...
- autoit 中文API
中文API 参考地址: http://www.jb51.net/shouce/autoit/ 虫师的selelnium里面也有简单的说 环境搭建+上传弹窗的小案例
- 学习笔记之.NET Core
source code https://github.com/haotang923/dotnet/tree/master/src .NET Documentation | Microsoft Docs ...
- 1001 A+B Format (20 分)
1001 A+B Format (20 分) Calculate a+b and output the sum in standard format -- that is, the digits mu ...
- El中调用静态方法
最近在项目中遇到需要调用静态方法的问题,形如: <c:forEach items="beans" var="bean"> <p>总数:$ ...