Python的程序结构[7] -> 生成器/Generator -> 生成器浅析
生成器 / Generator
目录
1 关于生成器
从计算机科学角度上看,生成器是一种类协程或半协程(Semi-coroutine),生成器提供了一种可以通过特定语句或方法来使生成器的执行对象(Execution)暂停,而这语句一般都是 yield。通过 yield 语句将每一次的结果切出执行对象,并带到主线程上来。yield 可以将一个值带出协程,而主线程也可以通过生成器对象的方法将一个值带回生成器的执行对象中去。
在 Python 中生成器常是一个由生成器函数产生的对象,可以使用 nex t函数调用其内部参数,直到所有参数均被生成返回耗尽,则返回一个 StopIteration 异常。在生成器中,返回参数值使用的不是 return 而是 yield,也正是这个 yield 使其所在的函数变成了一个生成器。
对于生成器来说,它不会把结果保存在特定序列中,而是将生成器的状态进行保存。每一次 yield 后都会保存生成器的上下文(包括内部状态及变量值),当下次迭代到来时,则从 yield 处继续生成器的后续函数,且内部状态均为上次 yield 前的状态。
2 生成器与迭代器
而对于生成器与迭代器的关系,可以说每一个生成器都是一个迭代器,反之不亦然。也可以认为,生成器是一个带有迭代器特性,并且具有一个或多个 yield 表达式的对象。
通过源码可以查看到,generator 的基类为 iterator。
3 生成器的建立
生成器的建立主要有以下两种:
1. 通过函数中的 yield 表达式,挂起函数;
2. 对于可迭代对象,使用[]推导时会遍历可迭代对象产生一个列表,而使用()推导时,会返回可迭代的对象作为一个生成器。
关于生成器的生成,可以使用以下两种方式,即分别使用()推导和 yield 进行生成
# Use () to replace [], make it into a generator
gen = (x**2 for x in range(7))
lis = [x**2 for x in range(7)]
print(type(gen), type(lis)) # Define function to create generator by using yield
# Return will cause StopIertation and return value will be present as an explaination for StopIteration
def gener(m):
n = 1
while n < m:
yield n
n += 1
if n == 3:
return 'Stop at 3' # gener is function, while gener(7) is generator
print(type(gener), type(gener(7)))
输出结果为
<class 'generator'> <class 'list'>
<class 'function'> <class 'generator'>
通过输出的第一行结果可以看到,通过[]推导得到的是一个 list ,而通过 ()推导得到的是一个生成器类,即返回的是生成器对象。
而对于 yield 方式生成的生成器,可以看到,生成生成器的 gener 是一个函数,而调用函数之后返回的结果 gener(m) 则是一个生成器。
同时,还可以通过 help 查看生成器内部的一些信息,
help(gener(10))
输出结果
Help on generator object: gener = class generator(object)
| Methods defined here:
|
| __del__(...)
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __next__(self, /)
| Implement next(self).
|
| __repr__(self, /)
| Return repr(self).
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| gi_code
|
| gi_frame
|
| gi_running
对于生成器来说,内部包含了许多内置方法,包括 iter() 会调用的 __iter__ 函数,next() 会调用的 __next__ 函数,这使得生成器遵循迭代器协议,以及3个可以直接调用的方法 close(), send(), throw()。
4 通过迭代生成器获取值
对于生成器的迭代可以由两种方式,第一种是使用 for 循环迭代,另一种是使用 next 获取下一个生成值,
# Iterate the generator
# Use "for"
for i in gener(7):
print(i)
g = gener(7)
# Use "next"
while g:
try:
print(next(g))
except StopIteration as e:
print(e)
break
输出结果
1
2
1
2
Stop at 3
此处结果可以看出,两种方式均成功让生成器运作,且在迭代到 3 时,生成器内部的 return 语句会抛出一个 StopIteration 异常,并且将返回值作为对异常的说明,可以通过异常捕获进行查看。
5 生成器的 close 方法
使用 close 方法可以将生成器进行关闭,并且后续的迭代操作将返回一个 StopIteration 的异常。
# Test close() function: close function will cause later iteration call a StopIteration
def gx():
yield 1
yield 2
yield 3 g = gx()
for i in range(3):
try:
c = next(g)
print(c)
except StopIteration:
print('Stop at %d' % c)
if c == 2:
g.close()
在 gx() 生成器中,会 yield 三个值,而在循环中,当调用 2 次 next() 函数后,便关闭生成器,同时对异常进行了捕获,最终查看结果
1
2
Stop at 2
可以看到生成器由于被 close() 方法关闭,最终停在了 2 处,第三次迭代并未成功。
6 生成器的 send 方法
首先定义的 gx() 函数会进入一个循环,每次 yield 之后,会将 send 函数传送的值赋值给 r。值得注意的是:
1. yield 实际上是会返回一个值的,当使用 next() 时,yield 返回的是一个 None 值;
2. 当向初次启动的生成器发送参数时(在此之前未对生成器进行过任何迭代,包括 next() 函数等),第一次 send() 发送的参数必须为 None,其原因在于,由于生成器在 send() 之前未被启动,因此当调用 send() 方法时,会启动生成器,此时挂起在 yield 处,当再次调用下一个 send() / next() 函数时, yield 才会将收到的值传入,而到此时为止,第一次 send() 函数发送的值都没有能够执行赋值或其他操作。因此便将 send(None) 作为第一次生成器启动的标志。而 next 函数由于原本就传入的是 None,因此可以启动生成器。
Note: 实质上生成器的 __next__() 函数调用的是 send(None) 函数。
下面的函数中,第一次传入非 None 参数将无法启动生成器,并且之后的所有非 None 参数均无法启动,只有当第一个 None 被传入后生成器才会开始运行,之后的每一个 None 都会被当成正常参数传入。
# Test send() function: send function will pass a value to generator
def gx():
i = 'Init'
while True:
r = yield i
if r == 'Stop':
print('Got: Stop, stop at here')
break
i = 'Got: %s' % r
g = gx()
for i in [0, None, None, 'First', 'Second', 'Third', 'Stop']:
try:
print(g.send(i))
except TypeError as e:
print('Send "%s" into g, and cause TypeError: %s.' % (i, e))
except StopIteration:
print('Send "%s" into g, and cause StopIteration.' % i)
输出结果
Send "" into g, and cause TypeError: can't send non-None value to a just-started generator.
Init
Got: None
Got: First
Got: Second
Got: Third
Got: Stop, stop at here
Send "Stop" into g, and cause StopIteration.
查看最终的结果可以发现,其工作过程为,首先传入的非 None 参数直接引起了生成器的 TypeError 异常,而随后的 send(None) 启动了生成器,第一次 yield 的参数 Init 被传出,但是传入的 None 未被使用,挂起在yield处且未进行赋值给r的操作,这时再次调用一个 send(None),此时传入的 None 会被作为参数赋给 r,并且执行下面的操作,改变 i 后执行 yield i 并挂起,等待下一个 send() / next()。
7 生成器的 throw 方法
throw() 方法可以向生成器内部传送一个异常,会将当前挂起处的 yield 消耗掉,若没有异常捕获的存在,则会引发生成器异常,若存在异常捕获则会进入 except 执行异常处理后继续程序运行直到下一个 yield 处等待。若无下一个 yield 则结束生成器。
def gx():
c = 1
while True:
try:
print('First yield')
yield c
c += 1
print('Second yield')
yield c
c += 1
print('Third yield')
yield c
c += 1
except ValueError:
print('Receive an ValueError, and c is %d now.' % c)
except TypeError:
print('Receive a TypeError, and c is %d now.' % c)
break
g = gx()
print('First time using next(g) got:', next(g), '\n')
print('Second time using g.throw() got:', g.throw(ValueError), '\n')
print('Thrid time using next(g) got:', next(g), '\n')
print('Forth time using g.throw() got:',g.throw(TypeError), '\n')
此处的第一个 next() 函数从 yield 处得到了 1,而第二次使用了 throw() 函数,传入了一个 ValueError ,此时挂起的第二个 yield c 将不会 yield,且由异常捕获而进入 except 语句中,执行异常捕获处理后再次回到循环挂起在第一个 yield c 处。随后正常调用,最后由 TypeError 结束循环。此时由于挂起的 yield 没有返回值且后续无 yield,因此抛出了 StopIteration 异常。
最终输出为
First yield
First time using next(g) got: 1 Receive an ValueError, and c is 1 now.
First yield
Second time using g.throw() got: 1 Second yield
Thrid time using next(g) got: 2 Receive a TypeError, and c is 2 now.
Traceback (most recent call last):
File "C:\Users\EKELIKE\Documents\Python Note\3_Program_Structure\3.7_Special_Structure\generator_demo.py", line 91, in <module>
print('Forth time using g.throw() got:',g.throw(TypeError), '\n')
StopIteration
8 空生成器的检测方法
在使用生成器时,通常会遇到一些情况,想要对当前的生成器进行判断,看生成器是否为空,从而根据生成器是否有内容来执行不同的操作。但是,此时会遇到一个问题,即如何得知生成器是否为空。通常会有以下几种思路,但这几种思路都不能很好的完成需求,
1. 直接 print 生成器 – 此时并不会显示出生成器的内容,而是显示这是一个 generator obj;
2. 通过迭代获取内部值看是否为空 – 迭代之后原生成器的值将被消耗,此时虽然能够判断生成器是否为空,但却已经消耗了生成器内部的值,甚至迭代完成后只留下一个空生成器;
3. 使用 list/tuple 等函数强制转换生成器为其他类型的数据结构,再对转换后的新数据进行操作。但这种方式若是在生成器包含数据量巨大时,生成的新数据结构将会占用很大的空间,此时便失去了使用生成器的意义。
参考了 Stack Overflow 上的一个解答,利用装饰器对生成器进行装饰后可以使得生成器在为空的时候返回 None 值
"""
Decorator for generator empty test
"""
import itertools def deco(f):
def wap(*args):
it = f(*args)
try:
first = next(it)
except StopIteration:
return None
return itertools.chain([first], it)
return wap @deco
def gen(g):
for i in g:
yield i x = gen([1, 2])
print("Generator is:", x)
for i in x:
print("Generator element:", i)
x = gen([])
print("Generator is:", x)
上面的装饰器函数 deco 首先获取生成器的第一个元素值,然后判断是否获取成功,若获取成功则将第一个值添加回原生成器中,再将新生成器返回,若获取失败则说明原来的生成器为空,此时返回 None 值。
查看运行输出的结果,可以看到,当生成器没有值时,此时能够返回 None。
Generator is: <itertools.chain object at 0x02EA9DD0>
Generator element: 1
Generator element: 2
Generator is: None
相关阅读
1. 迭代器
2. 装饰器
参考链接
http://www.jianshu.com/p/b709747d125e
https://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators
http://python.jobbole.com/81911/
Python的程序结构[7] -> 生成器/Generator -> 生成器浅析的更多相关文章
- Python的程序结构[6] -> 迭代器/Iterator -> 迭代器浅析
迭代器 / Iteratior 目录 可迭代对象与迭代器协议 迭代器 迭代器(模拟)的建立 1 可迭代对象与迭代器协议 对于迭代器首先需要了解两个定义,迭代器协议 (Iterator Protocol ...
- Python基本程序结构
条件判断: 计算机之所以能做很多自动化的任务,因为它可以自己做条件判断.比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:
- Python的程序结构[5] -> 模块/Module[0] -> 内建模块 builtins
builtins 内建模块 / builtins Module 在Python的模块中,有一种特殊模块,无需导入便可以使用,其中包含了许多内建函数与类. builtins 模块内容 / builtin ...
- Python的程序结构[0] -> 属性/Property[0] -> 类属性、实例属性和私有属性
类属性.实例属性和私有属性 Python中类的属性主要包括类属性,实例属性和私有属性,下面是对三种属性的简单介绍 类属性 / Class Property 类属性在__init__()之外初始化,在外 ...
- Python的程序结构[1] -> 方法/Method[0] -> 类实例方法、私有方法和抽象方法
类实例方法.私有方法和抽象方法 Python中最常用的就是类实例方法,类似于属性中的类实例属性,同时,也存在与私有属性类似方法,即私有方法,下面介绍这两种常见的方法,以及一种特殊意义的类实例方法 -- ...
- Python的程序结构[1] -> 方法/Method[1] -> 静态方法、类方法和属性方法
静态方法.类方法和属性方法 在 Python 中有三种常用的方法装饰器,可以使普通的类实例方法变成带有特殊功能的方法,分别是静态方法.类方法和属性方法. 静态方法 / Static Method 在 ...
- Python的程序结构[1] -> 方法/Method[2] -> 魔术方法 __init__ / __del__ / __new__
魔术方法 / Magic Method 魔法方法就是可以给你的类增加魔力的特殊方法(实质应称为特殊方法,魔术方法在JavaScript中有所体现,对象具有不透明特性,而且无法在自定义对象中模拟这些行为 ...
- Python的程序结构[2] -> 类/Class[0] -> 类的特殊属性
类的特殊属性 / Special Property of Class Python 中通过 class 进行类的定义,类可以实例化成实例并利用实例对方法进行调用. 类中还包含的一些共有的特殊属性. 特 ...
- Python的程序结构[2] -> 类/Class[1] -> 基类与继承
基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...
随机推荐
- 《Cracking the Coding Interview》——第18章:难题——题目5
2014-04-29 01:51 题目:你有一个文本文件,每行一个单词.给定两个单词,请找出这两个单词在文件中出现的其中一对位置,使得这两个位置的距离最短. 解法:我的思路是建立倒排索引,计算出所有单 ...
- 《Cracking the Coding Interview》——第11章:排序和搜索——题目5
2014-03-21 21:37 题目:给定一个字符串数组,但是其中夹杂了很多空串“”,不如{“Hello”, “”, “World”, “”, “”, “”, “Zoo”, “”}请设计一个算法在其 ...
- Python全栈工程师(列表、拷贝)
ParisGabriel 感谢 大家的支持 你们的阅读评价就是我最好的更新动力 我会坚持吧排版做的越来越好 每天坚持 一天一篇 点个订阅吧 灰常感谢 当个死粉也阔以 Py ...
- git使用及一些配置、问题
安装https://git-for-windows.github.io/ 一.绑定用户名.邮件地址 git config --global user.name "Your Name" ...
- HDU 4714 Tree2cycle 找规律
假设最少删除的边的个数为cost,显然,最终答案即为cost+cost+1 (因为删除一条边,就会增加一个链,所以删除cost条边后,就会有cost+1条链,将这cost+1条链连接起来的代价为cos ...
- Servlet 返回Json数据格式
其实就是把数据库中的数据查询出来拼接成一个Json数据 import dao.UserDao; import endy.User; import javax.servlet.ServletExcept ...
- 【bzoj5056】OI游戏 最短路+矩阵树定理
题目描述 给出一张无向图,求满足 0号点到所有点的路径长等于原图中它们之间最短路 的生成树的个数. 输入 第一行一个整数N,代表原图结点. 接下来N行,每行N个字符,描绘了一个邻接矩阵.邻接矩阵中, ...
- [CF1036C]Classy Numbers
题目大意:多个询问,每个询问问$[l,r](1\leqslant l\leqslant r\leqslant10^{18})$内有多少个数满足非零数位小于等于$3$. 题解:数位$DP$,$f_{i, ...
- poj 1764 Dice Contest
题目戳这里. 首先我要吐槽这个题目描述不清.\(2\)对着选手,那选手朝那边?看完别人写的程序后我才知道选手对着目标所在的方向(或左或右). 然后这道题还是不错的,因为他交给我矩阵乘法不只有常规意义下 ...
- js中哪些语句在if语句中默认为真
结论:js中有一个函数是:Boolean(value)这个函数把一个value值转换成相应的boolean值. 当value为以下值是为true:1.任意的非空字符串 .2.任意的非0数字 而当val ...