美文网首页
Python 中的 yield from 关键字

Python 中的 yield from 关键字

作者: DejavuMoments | 来源:发表于2018-12-08 23:55 被阅读60次

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

    相关文章

      网友评论

          本文标题:Python 中的 yield from 关键字

          本文链接:https://www.haomeiwen.com/subject/jqhycqtx.html