• python协程 yield和yield_from的区别


    面试被问到python3 的asyncio模块、python2中的yield和yield from的区别

    面的第一家觉得回答的不是很好, 回来进行收集整理一番,以便巩固记忆

    Python中的协程大概经历了三个阶段:

    • 最初的生成器变形yield/send
    • 引入@asyncio.coroutine和yield from
    • 在最近的python3.5版本中引入async/await字段

    下面将对上边的三个阶段进行说明

    1、 生成器变形yield/send

    普通函数中如果出现了yield, name该函数就不是普通的函数, 而是一个生成器

    1 def gen():
    2     for i in range(1,10):
    3         yield i
    4 
    5 g = gen()
    6 for i in g:
    7     print(i)

    像上面的代码 g 就是一个生成器, 生成器就是一种迭代器,可以使用for进行迭代,生成器最大的特点就是可以接受一个外部传入的变量,根据变脸内容计算返回结果

     1 def gen():
     2     while True:
     3         i = 0
     4         x = yield i
     5         if x == 0:
     6             break
     7         i = i*x
     8 g = gen()
     9 print(g.send(None))
    10 print(g.send(1))
    11 print(g.send(2))
    12 print(g.send(0))

    上面的步骤中, x = yield i最重要也最容易理解错,下面详细接介绍该语句的运行流程

    其实x = yield i包含了三个步骤:

    1. 向函数外抛出(返回) i 
    2. 暂停,等待next()和send()恢复
    3. 赋值receive=MockGetValue() 。 这个MockGetValue()是假想函数,用来接收send()发送进来的值

    执行流程:

    1. 通过g.send(None)或者next(g)启动生成器函数, 并执行到第一个yield语句停止,注意在这里相当于执行了上边的1,2步,程序返回yield后边的 i 值后暂停,并没有执行第三步, 注意第一次这里send(None)函数的参数必须传None否者会报错
    2. 通过g.send(1), 会传入值,从上一次暂停的位置继续执行,从第三步执行赋值操作后 再执行1,2步。
    3. 当我们传g.send(0)时候, 会主动break 并且退出循环,最后整个函数执行完毕,所以会得到StopIteration异常。

    2、yield from

     1 def g1():     
     2      yield  range(5)
     3 def g2():
     4      yield  from range(5)
     5 
     6 it1 = g1()
     7 it2 = g2()
     8 for x in it1:
     9     print(x)
    10 
    11 for x in it2:
    12     print(x)
    13 
    14 """
    15 会输出
    16 range(0, 5)
    17 0
    18 1
    19 2
    20 3
    21 4
    22 """

    个人理解yield仅仅是截断并返回,而yield from相当于解析了后边的可迭代对象,并返回每一个item。

    yield from iterable本质上等于for item in iterable: yield item的缩写版

    注意:yield_from 后边的必须为可迭代对象(iterable)

    3、 asyncio.cotoutine和yield from

    yield from在asyncio模块中得以发扬光大。之前都是我们手工切换协程,现在当声明函数为协程后,我们通过事件循环来调度协程。

    示例代码

     1 import asyncio,random
     2 @asyncio.coroutine
     3 def smart_fib(n):
     4     index = 0
     5     a = 0
     6     b = 1
     7     while index < n:
     8         sleep_secs = random.uniform(0, 0.2)
     9         yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
    10         print('Smart one think {} secs to get {}'.format(sleep_secs, b))
    11         a, b = b, a + b
    12         index += 1
    13 
    14 @asyncio.coroutine
    15 def stupid_fib(n):
    16     index = 0
    17     a = 0
    18     b = 1
    19     while index < n:
    20         sleep_secs = random.uniform(0, 0.4)
    21         yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
    22         print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
    23         a, b = b, a + b
    24         index += 1
    25 
    26 if __name__ == '__main__':
    27     loop = asyncio.get_event_loop()
    28     tasks = [
    29         smart_fib(10),
    30         stupid_fib(10),
    31     ]
    32     loop.run_until_complete(asyncio.wait(tasks))
    33     print('All fib finished.')
    34     loop.close()

    yield from语法可以让我们方便地调用另一个generator。

    本例中yield from后面接的asyncio.sleep()是一个coroutine(里面也用了yield from),所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
    asyncio是一个基于事件循环的实现异步I/O的模块。通过yield from,我们可以将协程asyncio.sleep的控制权交给事件循环,然后挂起当前协程;之后,由事件循环决定何时唤醒asyncio.sleep,接着向后执行代码。
    协程之间的调度都是由事件循环决定。
    yield from asyncio.sleep(sleep_secs) 这里不能用time.sleep(1)因为time.sleep()返回的是None,它不是iterable,还记得前面说的yield from后面必须跟iterable对象(可以是生成器,迭代器)。

    所以会报错:

    yield from time.sleep(sleep_secs)
    TypeError: ‘NoneType’ object is not iterable

    4、async和await

    弄清楚了asyncio.coroutine和yield from之后,在Python3.5中引入的async和await就不难理解了:可以将他们理解成asyncio.coroutine/yield from的完美替身。当然,从Python设计的角度来说,async/await让协程表面上独立于生成器而存在,将细节都隐藏于asyncio模块之下,语法更清晰明了。
    加入新的关键字 async ,可以将任何一个普通函数变成协程

    mport time,asyncio,random
    async def mygen(alist):
        while len(alist) > 0:
            c = randint(0, len(alist)-1)
            print(alist.pop(c))
    a = ["aa","bb","cc"]
    c=mygen(a)
    print(c)
    输出:
    <coroutine object mygen at 0x02C6BED0>

    在上面程序中,我们在前面加上async,该函数就变成一个协程了

    但是async对生成器是无效的。async无法将一个生成器转换成协程。

    还是刚才那段代码,我们把print改成yield

    async def mygen(alist):
        while len(alist) > 0:
            c = randint(0, len(alist)-1)
            yield alist.pop(c)
    a = ["ss","dd","gg"]
    c=mygen(a)
    print(c)
    # 可以看出:
    <async_generator object mygen at 0x02AA7170>

    可以看到输出:<async_generator object mygen at 0x02AA7170>

    并不是coroutine 协程对象

    所以我们的协程代码应该是这样的

    import time,asyncio,random
    async def mygen(alist):
        while len(alist) > 0:
            c = random.randint(0, len(alist)-1)
            print(alist.pop(c))
            await asyncio.sleep(1) 
    strlist = ["ss","dd","gg"]
    intlist=[1,2,5,6]
    c1=mygen(strlist)
    c2=mygen(intlist)
    print(c1)

    要注意的是await语法只能出现在通过async装饰的函数中否则会报SyntaxError错误, 而且await后边的对象需要是一个Awaitable,或者实现了相关的协议。

    查看Awaitable抽象类的代码,表明了只要一个类实现了__await__方法,那么通过它构造出来的实例就是一个Awaitable:

    要运行协程,要用事件循环

    在上面的代码下面加上:

    1 if __name__ == '__main__':
    2         loop = asyncio.get_event_loop()
    3         tasks = [
    4         c1,
    5         c2
    6         ]
    7         loop.run_until_complete(asyncio.wait(tasks))
    8         print('All fib finished.')
    9         loop.close()

    就可以看到交替执行的效果。

    本文参考:https://blog.csdn.net/soonfly/article/details/78361819

  • 相关阅读:
    cocos2dx3.0戳青蛙游戏(打地鼠)
    深入理解Tomcat系列之五:Context容器和Wrapper容器
    linux下拷贝隐藏文件
    8.8.1 运行计划
    UVALive
    堆排序实现
    C语言中的signal函数
    uboot和内核分区的改动
    Android缩放动画
    .Net 自定义应用程序配置
  • 原文地址:https://www.cnblogs.com/musl/p/12993448.html
Copyright © 2020-2023  润新知