Python 3.3 新增 yield from 语法,是理解协程的基础
#0 了解一下 itertools.chain
itertools.chain() 方法可以将不同的迭代类型连接起来进行 for 循环遍历。
from itertools import chain
lst = [1,2,3]
dic = {
"tom":"www.tom.com",
"bob":"bob.com"
}
# 使用 chain 方法可以直接对三个进行 for 循环
for value in chain(lst,dic,range(5,10)):
print(value)
>>>
1
2
3
tom
bob
5
6
7
8
9
那么 itertools.chain() 是如何实现的呢?
# chain 的实现方法
# *args
# **kwargs
def chainer(*args, **kwargs):
for iterable in args:
for value in iterable:
yield value
for value in chainer(lst,dic,range(5,10)):
print(value)
>>>
1
2
3
tom
bob
5
6
7
8
9
有了 yield from
之后,我们还可以进一步少些代码:
# chain 的实现方法
# *args
# **kwargs
def chainer(*args, **kwargs):
for iterable in args:
yield from iterable
for value in chainer(lst,dic,range(5,10)):
print(value)
>>>
1
2
3
tom
bob
5
6
7
8
9
看,效果其实一模一样!应该可以大致感觉到yield from
的特性了吧!yield from
后面操作一个 iterable 类型的对象可以直接获取到 iterable 类型的对象的值并返回。而 yield
则不一样!
#1 yield from
和 yield
的区别
def g1(iterable):
yield range(10)
for v in g1(range(10)):
print(v)
>>>
range(0, 10)
def g2(iterable):
yield from range(10)
for v in g2(range(10)):
print(v)
>>>
0
1
2
3
4
5
6
7
8
9
#2 yield from
高级特性及 Coroutine 实现
1.main
调用方,func_yield_from
委托生成器, iterable
子生成器
2.yield from
会在调用方和子生成器直接建立一个通道,双向通道,可以互相通
信
def func_yield_from(iterable):
yield from iterable
def main():
g = func_yield_from(range(10))
g.send(None)
看一个具体的实例:
final_result = {}
# 子生成器
def sum_sales(product_name):
total = 0
nums = []
while True:
x = yield
if x:
print(product_name + "销量:", x)
# 当 send None 的时候,跳出 while 循环
if not x:
break
total += x
nums.append(x)
return total, nums
# 委托生成器
def middle(key):
while True:
final_result[key] = yield from sum_sales(key)
print("Complete!",final_result[key])
# 调用方
def main():
dataset = {
'apple': [1200, 1330, 500],
'orange': [400, 600, 1000],
'banana': [100, 500, 50]
}
for key,dataset in dataset.items():
print('key:', key)
print('dataset:',dataset)
m = middle(key)
# 预激 middle 协程
m.send(None)
# 给协程传递每一组的值
for value in dataset:
m.send(value)
# 传递 None 终止 while 循环
m.send(None)
print(final_result)
if __name__ == "__main__":
main()
输出如下:
>>>
key: apple
dataset: [1200, 1330, 500]
apple销量: 1200
apple销量: 1330
apple销量: 500
Complete! (3030, [1200, 1330, 500])
key: orange
dataset: [400, 600, 1000]
orange销量: 400
orange销量: 600
orange销量: 1000
Complete! (2000, [400, 600, 1000])
key: banana
dataset: [100, 500, 50]
banana销量: 100
banana销量: 500
banana销量: 50
Complete! (650, [100, 500, 50])
{'apple': (3030, [1200, 1330, 500]), 'orange': (2000, [400, 600, 1000]), 'banana': (650, [100, 500, 50])}
#3 yield from
背后做的事情
-
try-catch
异常
# 没有 yield 的情况
def sum_sales(product_name):
total = 0
nums = []
while True:
x = yield
if x:
print(product_name + "销量:", x)
# 当 send None 的时候,跳出 while 循环
if not x:
break
total += x
nums.append(x)
return total, nums
if __name__ == "__main__":
gen = sum_sales('apple')
gen.send(None)
gen.send(1200)
gen.send(1000)
gen.send(800)
# gen.send(None)
>>>
apple销量: 1200
apple销量: 1000
apple销量: 800
去掉 gen.send(None)
注释之后:
>>>
apple销量: 1200
apple销量: 1000
apple销量: 800
Traceback (most recent call last):
File "/4_yield_from_try_catch_except.py", line 27, in
gen.send(None)
StopIteration: (3000, [1200, 1000, 800])
可见抛出了一个异常,并返回了我们需要的值。所以,在没有 yield from
的情况下,我们需要捕获这个异常:
if __name__ == "__main__":
gen = sum_sales('apple')
gen.send(None)
gen.send(1200)
gen.send(1000)
gen.send(800)
try:
gen.send(None)
except StopIteration as e:
result = e.value
print(result)
这样,就能正常了,不仅不会抛出异常,还能够获取到我们需要的值了:
apple销量: 1200
apple销量: 1000
apple销量: 800
(3000, [1200, 1000, 800])
所以说,单纯不用yield from
的话,我们就需要写异常处理之类的逻辑,进一步减少代码量。