Python 中的 yield from 关键字

2018-12-08  本文已影响60人  DejavuMoments

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 fromyield 的区别

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 背后做的事情

  1. 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 <module>
    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的话,我们就需要写异常处理之类的逻辑,进一步减少代码量。

上一篇下一篇

猜你喜欢

热点阅读