生成器 / Generator


目录

  1. 关于生成器
  2. 生成器与迭代器
  3. 生成器的建立
  4. 通过迭代生成器获取值
  5. 生成器的 close 方法
  6. 生成器的 send 方法
  7. 生成器的 throw 方法
  8. 空生成器的检测方法

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 -> 生成器浅析的更多相关文章

  1. Python的程序结构[6] -> 迭代器/Iterator -> 迭代器浅析

    迭代器 / Iteratior 目录 可迭代对象与迭代器协议 迭代器 迭代器(模拟)的建立 1 可迭代对象与迭代器协议 对于迭代器首先需要了解两个定义,迭代器协议 (Iterator Protocol ...

  2. Python基本程序结构

    条件判断: 计算机之所以能做很多自动化的任务,因为它可以自己做条件判断.比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:

  3. Python的程序结构[5] -> 模块/Module[0] -> 内建模块 builtins

    builtins 内建模块 / builtins Module 在Python的模块中,有一种特殊模块,无需导入便可以使用,其中包含了许多内建函数与类. builtins 模块内容 / builtin ...

  4. Python的程序结构[0] -> 属性/Property[0] -> 类属性、实例属性和私有属性

    类属性.实例属性和私有属性 Python中类的属性主要包括类属性,实例属性和私有属性,下面是对三种属性的简单介绍 类属性 / Class Property 类属性在__init__()之外初始化,在外 ...

  5. Python的程序结构[1] -> 方法/Method[0] -> 类实例方法、私有方法和抽象方法

    类实例方法.私有方法和抽象方法 Python中最常用的就是类实例方法,类似于属性中的类实例属性,同时,也存在与私有属性类似方法,即私有方法,下面介绍这两种常见的方法,以及一种特殊意义的类实例方法 -- ...

  6. Python的程序结构[1] -> 方法/Method[1] -> 静态方法、类方法和属性方法

    静态方法.类方法和属性方法 在 Python 中有三种常用的方法装饰器,可以使普通的类实例方法变成带有特殊功能的方法,分别是静态方法.类方法和属性方法. 静态方法 / Static Method 在 ...

  7. Python的程序结构[1] -> 方法/Method[2] -> 魔术方法 __init__ / __del__ / __new__

    魔术方法 / Magic Method 魔法方法就是可以给你的类增加魔力的特殊方法(实质应称为特殊方法,魔术方法在JavaScript中有所体现,对象具有不透明特性,而且无法在自定义对象中模拟这些行为 ...

  8. Python的程序结构[2] -> 类/Class[0] -> 类的特殊属性

    类的特殊属性 / Special Property of Class Python 中通过 class 进行类的定义,类可以实例化成实例并利用实例对方法进行调用. 类中还包含的一些共有的特殊属性. 特 ...

  9. Python的程序结构[2] -> 类/Class[1] -> 基类与继承

    基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...

随机推荐

  1. 《Cracking the Coding Interview》——第1章:数组和字符串——题目4

    2014-03-18 01:36 题目:给定一个字符串,将其中的空格‘ ’替换为‘%20’,你可以认为字符串尾部有足够空间来容纳新增字符.请不要额外开辟数组完成. 解法:先从前往后统计空格个数,然后从 ...

  2. linux下给开启端口

    首先在这里要推荐一篇博文 http://blog.csdn.net/zht666/article/details/17505789 这篇文章写的很详细,里面包含了操作端口一些命令,我们操作端口其实就是 ...

  3. 在SqlServer中通过SQL语句实现树状查询

    CREATE PROCEDURE [dbo].[GetTree] @Id int AS BEGIN with cte as ( as lvl from Entity where Id = @Id un ...

  4. web自动化测试,定位不到元素的原因及解决方案(持续更新中2018年9月29日)

    主要讲自己在实战中遇到的坑: 1.动态id定位不到元素 分析原因:每次打开页面,ID都会变化.用ID去找元素,每次刷新页面ID都会发生变化. 解决方案:推荐使用xpath的相对路径方法或者cssSel ...

  5. ul中li元素横向排列且不换行

    ul { white-space: nowrap; } li { display: inline-block; }     white-space 属性设置如何处理元素内的空白. normal 默认. ...

  6. python自动化运维篇

    1-1 Python运维-课程简介及基础 1-2 Python运维-自动化运维脚本编写 2-1 Python自动化运维-Ansible教程-Ansible介绍 2-2 Python自动化运维-Ansi ...

  7. nosetests

    1.nosetests 执行出测试报告 提前安装 插件nose-html系列插件   nosetests -v --with-html-output --html-out-file=报告名.html ...

  8. ironic-conductor与ipa交互clean部分代码分析

    clean的动作会在provide和delete阶段才会触发 从代码分析: 对节点执行的node provide/deleted/clean会先发送到ironicclient ironicclient ...

  9. [DM8168]Linux下SPI驱动测试

    1.内核自带的SPI相关的驱动文件 项目中有CPU与FPGA进行通信,用到SPI接口: SPI头文件在: linux-kernel/include/linux/spi.h SPI实现在: linux- ...

  10. DevExpress的GridControl控件设置自定义显示方法

    比如要显示性别为字符串,数据库中保存为数值(1:男,2:女,3:未知). 方法一: 点击控件上的"Run Designer"按钮,进入设计界面. 选择“Columns", ...