python - 生成器 2

生成器进化成协程

生成器是由迭代器进化而来,所以生成器对象有 iternext 方法,可以使用 for 循环获得值,注意这里所说的 “获得值” 指的是下文代码块里 yield 语句中 yield 关键字后面的 i 。这是在 Python 2.5 时出现的特性,在 Python 3.3 中出现 yield from 语法之前,生成器没有太大用途。


但此时 yield 关键字还是实现了一些特性,且至关重要,就是生成器对象有 send 、throw 和 close 方法。这三个方法的作用分别是发送数据给生成器并赋值给 yield 语句、向生成器中抛入异常由生成器内部处理、终止生成器。这三个方法使得生成器进化成协程。


协程有四种存在状态:
GEN_CREATED 创建完成,等待执行
GEN_RUNNING 解释器正在执行(这个状态在下面的示例程序中无法看到)
GEN_SUSPENDED 在 yield 表达式处暂停
GEN_CLOSE 执行结束,生成器停止
可以使用 inspect.getgeneratorstate 方法查看协程的当前状态,举例如下:
inspect模块用于收集python对象的信息,可以获取类或函数的参数的信息,源码,解析堆栈,对对象进行类型检查等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import inspect

def generator():
i = '激活生成器'
while True:
try:
value = yield i
except ValueError:
print('OVER')
i = value


g = generator() # 1
inspect.getgeneratorstate(g) # 2
print(next(g)) # 3
inspect.getgeneratorstate(g)
print(g.send('Hello Shiyanlou')) # 4
g.throw(ValueError) # 5

g.close() # 6

inspect.getgeneratorstate(g)





代码说明如下:
1、创建生成器
2、查看生成器状态
3、这步操作叫做预激生成器(或协程),这是必须做的。在生成器创建完成后,需要将其第一次运行到 yield 语句处暂停
4、暂停状态的生成器可以使用 send 方法发送数据,此方法的参数就是 yield 表达式的值,也就是 yield 表达式等号前面的 value 变量的值变成 ‘Hello Shiyanlou’,继续向下执行完一次 while 循环,变量 i 被赋值,继续运行下一次循环,yield 表达式弹出变量 i
5、向生成器抛入异常,异常会被 try except 捕获,作进一步处理
6、close 方法终止生成器,异常不会被抛出


因为生成器的调用方也就是程序员自己可以控制生成器的启动、暂停、终止,而且可以向生成器内部传入数据,所以这种生成器又叫做协程,generator 函数既可以叫做生成器函数,也可以叫协程函数,这是生成器向协程的过渡阶段。

yield -> yield from

在 Python 3.3 中新增了 yield from 语法,如果将yield理解成“返回”,那么yield from就是“从什么(生成器)里面返回”,这是全新的语言结构,是 yield 的升级版。相比 yield ,该语法有两大优势,我们来举例说明它的用法。

区别示例

1
2
3
4
5
6
7
8
9
10
11
12
def generator():
yield 'a'
yield 'b'
yield 'c'
yield from generator1() #yield from iterable本质上等于 for item in iterable: yield item的缩写版
yield from [11,22,33,44]
yield from (12,23,34)
yield from range(3)


for i in generator():
print(i,end=' , ')

避免潜逃循环

yield:

1
2
3
4
5
6
7
8
9
10
def chain(*args):
for iter_obj in args:
for i in iter_obj:
yield i

chain({'one', 'two'}, list('ace'))


for i in c:
print(i)


yield from:

1
2
3
4
5
6
7
8
9
def chain(*args):
for iter_obj in args:
yield from iter_obj

c = chain({'one', 'two'}, list('ace'))


for i in c:
print(i)


可以看到 yield from 语句可以替代 for 循环,避免了嵌套循环。同 yield 一样,yield from 语句也只能出现在函数体内部,有 yield from 语句的函数叫做协程函数或生成器函数。


yield from 后面接收一个可迭代对象,例如上面代码中的 iter_obj 变量,在协程中,可迭代对象往往是协程对象,这样就形成了嵌套协程。

转移控制权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import time
from faker import Faker
from functools import wraps

# 预激协程装饰器
def coroutine(func):
@wraps(func)
def wrapper(*args, **kw):
g = func(*args, **kw)
next(g)
return g
return wrapper

# 子生成器函数,这个生成器是真正做事的生成器
def sub_coro():
l = [] # 创建空列表
while True: # 无限循环
value = yield # 调用方使用 send 方法发生数据并赋值给 value 变量
if value == 'CLOSE': # 如果调用方发生的数据是 CLOSE ,终止循环
break
l.append(value) # 向列表添加数据
return sorted(l) # 返回排序后的列表

# 使用预激协程装饰器
# 带有 yield from 语句的父生成器函数
@coroutine
def dele_coro():
# while True 可以多次循环,每次循环会创建一个新的子生成器 sub_coro()
# 这里 while 只循环一次,这是由调用方,也就是 main 函数决定的
# while 循环可以捕获函数本身创建的父生成器终止时触发的 StopIteration 异常
while True:
# yield from 会自动预激子生成器 sub_coro()
# 所以 sub_coro 在定义时不可以使用预激协程装饰器
# yield from 将捕获子生成器终止时触发的 StopIteration 异常
# 并将异常的 value 属性值赋值给等号前面的变量 l
# 也就是 l 变量的值等于 sub_coro 函数的 return 值
# yield from 还实现了一个重要功能
# 就是父生成器的 send 方法将发送值给子生成器
# 并赋值给子生成器中 yield 语句等号前面的变量 value
l = yield from sub_coro()
print('排序后的列表:', l)
print('------------------')

# 调用父生成器的函数,也叫调用方
def main():
# 生成随机国家代号的方法
fake = Faker().country_code
# 嵌套列表,每个子列表中有三个随机国家代号(字符串)
nest_country_list = [[fake() for i in range(3)] for j in range(3)]
for country_list in nest_country_list:
print('国家代号列表:', country_list)
c = dele_coro() # 创建父生成器
for country in country_list:
c.send(country) # 父生成器的 send 方法将国家代号发送给子生成器
# CLOSE 将终止子生成器中的 while 循环
# 子生成器的 return 值赋值给父生成器 yield from 语句中等号前面的变量 l
c.send('CLOSE')

if __name__ == '__main__':
main()


所谓 “转移控制权” 就是 yield from 语法可以将子生成器的控制权交给调用方 main 函数,在 main 函数内部创建父生成器 c ,控制 c.send 方法传值给子生成器。这是一个巨大的进步,在此基础上,Python 3.4 新增了创建协程的装饰器,这样非生成器函数的协程函数就正式出现了。