协程

可迭代

可迭代对象,是其内部实现了,__iter__ 这个魔术方法。

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
In [18]: a = [1, 2, 3, 4, 5]

In [19]: dir(a)
Out[19]:
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']

迭代器

可以使用 iter 直接将一个可迭代的对象转换为 迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In [20]: b = iter(a)

In [21]: type(b)
Out[21]: list_iterator

In [22]: next(b)
Out[22]: 1

In [23]: next(b)
Out[23]: 2

In [24]: next(b)
Out[24]: 3

In [25]: next(b)
Out[25]: 4

对比可迭代对象,迭代器其实就只是多了一个函数而已。就是__next__(),我们可以不再使用for循环来间断获取元素值。而可以直接使用next()方法来实现。

可以通过,dir()方法来查看是否有__next__来判断一个变量是否是迭代器的 。

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
In [26]: dir(b)
Out[26]:
['__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__']

生成器

前面我们说,迭代器,是在可迭代的基础上,加了一个next()方法。 而生成器,则是在迭代器的基础上(可以用for循环,可以使用next()),再实现了yieldyield 是什么东西呢,它相当于我们函数里的 return。在每次next(),或者for遍历的时候,都会 yield 这里将新的值返回回去,并在这里阻塞,等待下一次的调用。

创建生成器

  1. 使用列表生成式

    1
    2
    # 使用列表生成式,注意不是[],而是()
    L = (x * x for x in range(10))
  2. 实现 yield 的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 实现了yield的函数
    def mygen(n):
    now = 0
    while now < n:
    yield now
    now += 1

    if __name__ == '__main__':
    gen = mygen(10)

    可迭代对象和迭代器,是将所有的值都生成存放在内存中,而生成器则是需要元素才临时生成,节省时间,节省空间

运行生成器

  1. 使用next()
  2. 使用generator.send(None)

    1
    In [50]: a = mygen(10)

In [51]: next(a)
Out[51]: 0

In [52]: next(a)
Out[52]: 1

In [53]: a.send(None)
Out[53]: 2

In [54]: a.send(None)
Out[54]: 3

1
2
3
4
5
6
7
8
9
10



#### 生成器生命周期

​```python
GEN_CREATED # 等待开始执行
GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处暂停
GEN_CLOSED # 执行结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [56]: from inspect import getgeneratorstate

In [57]: c = mygen(10)

In [58]: getgeneratorstate(c)
Out[58]: 'GEN_CREATED'

In [59]: next(c)
Out[59]: 0

In [60]: getgeneratorstate(c)
Out[60]: 'GEN_SUSPENDED'

In [61]: next(c)
Out[61]: 1

In [62]: getgeneratorstate(c)
Out[62]: 'GEN_SUSPENDED'

协程 yield

通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(yield)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None))。这种向暂停的生成器发送信息的功能通过 PEP 342 进入 Python 2.5 中,并催生了 Python协程的诞生。协程通过使用 yield 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def jumping_range(N):
index = 0
while index < N:
# 通过send()发送的信息将赋值给jump
jump = yield index
if jump is None:
jump = 1
index += jump

if __name__ == '__main__':
itr = jumping_range(5)
print(next(itr)) # N=5,index=0,返回 index=0
print(itr.send(2)) # N=5,jump=2,index=0+2,返回2
print(next(itr)) # N=5,index=2,jump=None,jump=1,index=2+1,返回3
print(itr.send(-1)) # N=5,index=3,jump=-1,index=2,返回2

yield index 是将index return给外部调用程序。jump = yield 可以接收外部程序通过send()发送的信息,并赋值给jump

输出:

1
2
3
4
0
2
3
2

yield from

用法

yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。

yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

我们可以用一个使用yield和一个使用yield from的例子来对比看下 :

使用yield

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))

def gen(*args, **kw):
for item in args:
for i in item:
yield i

new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

使用yield from :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))

def gen(*args, **kw):
for item in args:
yield from item

new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

由上面两种方式对比,可以看出,yield from 后面加上可迭代对象,生成器等。他可以把可迭代对象里的每个元素一个一个的 yield 出来,对比 yield 来说代码更加简洁,结构更加清晰。

yield from 后面加上一个生成器后,就实现了生成的嵌套。当然实现生成器的嵌套,并不是一定必须要使用yield from,而是使用yield from可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。

1、调用方:调用委派生成器的客户端(调用方)代码

2、委托生成器:包含yield from表达式的生成器函数

3、子生成器:yield from后面加的生成器函数

这个例子,是实现实时计算平均值的。 比如,第一次传入10,那返回平均数自然是10. 第二次传入20,那返回平均数是(10+20)/2=15 第三次传入30,那返回平均数(10+20+30)/3=20 。

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
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count

# 委托生成器
def proxy_gen():
while True:
yield from average_gen()

# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0

if __name__ == '__main__':
main()

委托生成器的作用是:在调用方与子生成器之间建立一个双向通道。所谓的双向通道是什么意思呢?
调用方可以通过send()直接发送消息给子生成器,而子生成器 yield 的值,也是直接返回给调用方。你可能会经常看到有些代码,还可以在yield from前面看到可以赋值。这是什么用法? 你可能会以为,子生成器 yield 回来的值,被委托生成器给拦截了,但其实并不是这样,因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道,它并没有权利也没有办法,对子生成器 yield 回来的内容做拦截。

为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释:

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
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count

# 每一次return,都意味着当前协程结束。
return total,count,average

# 委托生成器
def proxy_gen():
while True:
# 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
total, count, average = yield from average_gen()
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))

# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
calc_average.send(None) # 结束协程
# 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程

if __name__ == '__main__':
main()

输出

1
2
3
4
5
10.0
15.0
20.0
计算完毕!!
总共传入 3 个数值, 总和:60,平均数:20.0

到底yield from 有什么过人之处,让我们非要用它不可, 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使yield from那样省心。

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
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
return total,count,average

# 调用方
def main():
calc_average = average_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0

# ----------------注意-----------------
try:
calc_average.send(None)
except StopIteration as e:
total, count, average = e.value
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
# ----------------注意-----------------

if __name__ == '__main__':
main()