python 生成器小结
对python中的生成器进行下总结。
在python中生成器可以很方便的实现迭代协议。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。也就是说,yield是一个语法糖,内部实现支持了迭代器协议。
生成器的强大之一就在于他提供了协同程序的概念,协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。同时调用者也可以向程序传入额外的数据或者异常等,传入完毕后仍能在上次暂停的地方继续执行。
简单的生成器特性
我先以上一篇的列表的例子实现一个生成器:1
2
3def ListGenerator(data):
for i in data:
yield i
之所以我把函数写的像类一样,是因为含有yield
的函数并不是一般的函数,它更像一个类,因为它返回一个对象,这个对象就叫生成器。
现在我生成一个生成器对象:1
2
3
4
5
6In [1]: from list_iter import *
In [2]: a = ListGenerator([1,2,3,4,5])
In [3]: a
Out[3]: <generator object ListGenerator at 0x035DA6C0>
这样我们可以像使用迭代器一样去使用生成器对象:1
2In [5]: [i for i in a]
Out[5]: [1, 2, 3, 4, 5]
生成器的加强特性
在python2.5中一些加强特性加入到生成器中,让生成器更加的强大,所以除了__next__()
可以获取下一个生成的值,用户可以将值回送个生成器(send()
),在生成器抛出异常,以及要求生成器退出(close()
)
send()
方法
由于双向的动作涉及到一个叫send()
的代码来向生成器发送值,因此现在yield
必须是一个表达式,因为函数会在执行yield
之后暂停,等他回来的时候必须要有一个对象接手值,因此要写成a = yield b
这样。
举个简单的例子:1
2
3
4
5
6
7def ListGenerator(data):
for i in data:
receive = (yield i)
if receive:
print("receive {}.".format(receive))
else:
print("receive nothing.")
执行的结果:1
2
3
4
5
6
7
8
9
10
11
12In [47]: a = ListGenerator([1,2,3])
In [48]: a.__next__()
Out[48]: 1
In [49]: a.__next__()
receive nothing.
Out[49]: 2
In [50]: a.send(1.3)
receive 1.3.
Out[50]: 3
简单分析下上面这段程序就很清楚了:
创建生成器对象
首先使用函数ListGenerator
,python看到函数里有yield
关键字就知道这个函数不一般要返回一个生成器对象,并赋值给变量a
。第一次调用
__next__()
然后a就是个迭代器对象,它实现了迭代协议(即实现了__iter__()
和__next__()
方法,其中__iter__()
返回生成器自身),我们调用__next__()
函数函数体就开始执行,当执行到1
receive = (yield i)
python看到了
yield
就把i
返回并暂停函数执行。这也就是为什么第一次调用__next__()
时先只输出了1。第二次调用
__next__()
当再次调用__next__()
函数时候,函数便会在上次停止的地方继续执行,上次执行到了哪里?yield
执行之后是一个赋值操作,要把一个值赋给receive
,但是这时候我们没有给生成器发送值,因此python默认将这个值赋值为None
。然后进行下面的判断操作,由于receive
的值为None
所以直接打印receive nothing.
,然后继续执行,这时i
变为2并在再次遇到yield
生成器返回2并暂停。调用
send()
方法
这是在生成器恢复执行之前,我调用了生成器的send()
函数,向生成器传入了一个值1.3,传入之后生成器恢复函数的执行,将我传入的值赋值给receive
然后接着向下进行判断这是由于receive
的值是1.3了因此输出receive 1.3.
。然后接着第三轮迭代输出3.
throw
方法
throw
主要是向生成器发送异常,我将上面的代码进行了修改,是生成器能够捕获异常:1
2
3
4
5
6
7
8
9
10def ListGenerator(data):
for i in data:
try:
receive = (yield i)
if receive:
print("receive {}.".format(receive))
else:
print("receive nothing.")
except ValueError:
print("receive a ValueError.")
在shell中执行:1
2
3
4
5
6
7
8
9
10In [1]: from list_iter import *
In [2]: a = ListGenerator([1,2,3,4])
In [3]: a.__next__()
Out[3]: 1
In [4]: a.throw(ValueError)
receive a ValueError.
Out[4]: 2
当第一次调用__next__()
的时候,执行到yield
返回1并暂停,第二次我传入了一个VauleError
异常,生成器继续执行函数,由于接收到一个异常,并被下面的except ValueError
捕获,这时便输出了recieve a ValueError.
然后接着执行到yield
返回2。
close()
方法
当一个生成器是一个永远执行的时候(while True
的时候),我们就用到了close()
来终止它。
通过生成器,可以快速创建一个可迭代对象。
上一篇中提到过,我们可以通过将可迭代对象中的__iter__()
方法返回一个迭代器对象来实现多次重复迭代。有了生成器,我们可以让可迭代对象的__iter__()
方法直接返回一个生成器,也就是在__iter__()
中使用yield
关键字,这样创建迭代对象就很方便。我之前处理VASP文件的库VASPy中就是这样使用来从大文件中获取数据的,代码不贴上来了,直接放个链接吧: VASPy/atomco.py at master · PytLab/VASPy