python协程 yield和yield_from的区别

时间:2020-05-30
本文章向大家介绍python协程 yield和yield_from的区别,主要包括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

原文地址:https://www.cnblogs.com/musl/p/12993448.html