python async await报错_python-复盘-从yield/send到yield from再到async/await

本文介绍了Python中协程的发展历程,包括yield/send的使用,yield from的引入,以及async/await的关键字。通过示例解释了它们在生成器和异步编程中的作用,特别是asyncio模块在协程调度中的重要性。
摘要由CSDN通过智能技术生成

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

最初的生成器变形yield/send

引入@asyncio.coroutine和yield from

在最近的Python3.5版本中引入async/await关键字

一、生成器变形yield/send

In [31]: def gen():

...: value = 888168

...: while 1:

...: rece = yield value

...: if rece == 'e':

...: break

...: value = 'got: %s' % rece

...:

In [32]: g = gen()

In [33]: g.send(None) # 如何理解第一次的None?

Out[33]: 888168

In [34]: g.send('hello')

Out[34]: 'got: hello'

In [35]: g.send('e')

---------------------------------------------------------------------------

StopIteration Traceback (most recent call last)

in ()

----> 1 g.send('e')

StopIteration:

这个协程很简单,关键是理解第一步None:

None在计算机中表示 无 的意思,说白了 send(None)并没有发送None,因为None就是啥都无,它的本意是启动这个生成器。好了,如何启动?

第一次启动是按顺序执行 gen() 的。先是执行value = 888168,然后继续,直到 rece = yield value 的 yield value部分停止。注意是 yield value部分停止。所以send(None)的结果才是888168。yield value 是暂停点,rece = yield是下次的启动点。

二、yield from

In [36]: def g1():

...: yield range(4) # range 中元素未释放出,你是无法 yield 的

In [37]: def g2():

...: yield from range(4) # 把 range 中元素释放出来再yield成一个生成器

In [38]: it1 = g1()

In [39]: it2 = g2()

In [40]: for x in it1: # 某种意思上等同一个生成器套娃,你一次 for in 不行的。

...: print(x)

...:

range(0, 4)

In [41]: for x in it2: # 释放生成器元素成果

...: print(x)

...:

0

1

2

3

这说明yield就是将range这个可迭代对象直接返回了。

而yield from解析了range对象,将其中每一个item返回了。

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

来看一下例子,假设我们已经编写好一个斐波那契数列函数

In [42]: def fab(max): # 定义一个生成器

...: n,a,b = 0, 0, 1

...: while n < max:

...: yield b

...: a, b = b, a+ b

...: n = n + 1

In [43]: f = fab(4)

In [44]: f

Out[44]:

In [45]: def f_wrapper(fun_iterable):

...: print('start')

...: for item in fun_iterable: # for item 就是把生成器的元素给释放出来了

...: yield item # 但这里,yield又把释放的元素变成了生成器

...: print('end')

In [46]: wrap = f_wrapper(fab(5))

In [47]: for i in wrap: # 所以最后打印时候需要 for i in 来释放元素

...: print(i, end='')

...:

start

11235end # 结果就是1,1,2,3,5

In [48]: import logging

In [49]: def f_wrapper2(fun_iterable):

...: print('start')

...: yield from fun_iterable # 等同上方代码,把元素释放出来,重新yield后再组成新生成器

...: print('end')

In [50]: wrap = f_wrapper2( fab(5) )

In [52]: for i in wrap: # 释放元素

...: print(i, end='')

...:

start

11235end

yield from 后面必须跟iterable对象(可以是生成器,迭代器)

三、asyncio.coroutine和yield from

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

先看示例代码

import asyncio,random

@asyncio.coroutine

def smart_fib(n):

index = 0

a = 0

b = 1

while index < n:

sleep_secs = random.uniform(0, 0.2)

yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作

print('Smart one think {} secs to get {}'.format(sleep_secs, b))

a, b = b, a + b

index += 1

@asyncio.coroutine

def stupid_fib(n):

index = 0

a = 0

b = 1

while index < n:

sleep_secs = random.uniform(0, 0.4)

yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作

print('Stupid one think {} secs to get {}'.format(sleep_secs, b))

a, b = b, a + b

index += 1

if __name__ == '__main__':

loop = asyncio.get_event_loop()

tasks = [

smart_fib(10),

stupid_fib(10),

]

loop.run_until_complete(asyncio.wait(tasks))

print('All fib finished.')

loop.close()

结果:

stupid one think 0.16154263754880197 secs to get 1 # stu花费0.16

stupid one think 0.05444056751627722 secs to get 1 # stu花费0.05 + 0.16 = 0.21

stupid one think 0.0021981642884256304 secs to get 2 # stu花费0.21 + 0.002 = 0.212

smart one think 0.37663551871717216 secs to get 1 # sma花费 0.376,随机生成的0.37>0.21,所以smart才在这时候开始执行

smart one think 0.2645472221109239 secs to get 1 # 这就是异步高效的秘密,谁快先执行

smart one think 0.15856033283994533 secs to get 2

stupid one think 0.6443246613879796 secs to get 3

stupid one think 0.02350589883775811 secs to get 5

smart one think 0.1801088550985621 secs to get 3

smart one think 0.3576929865189507 secs to get 5

stupid one think 0.569321733443974 secs to get 8

stupid one think 0.008074465892759975 secs to get 13

smart one think 0.33219791971546947 secs to get 8

stupid one think 0.23956673657999528 secs to get 21

stupid one think 0.011677097683787352 secs to get 34

stupid one think 0.06350886018228774 secs to get 55

smart one think 0.13317102581385262 secs to get 13

smart one think 0.21706707561261382 secs to get 21

smart one think 0.2992267724097271 secs to get 34

smart one think 0.24725642731182954 secs to get 55

all fib finished

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

四、async和await

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

加入新的关键字 async ,可以将任何一个普通函数变成协程

import 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)

输出: # 只是协程,不是异步,异步必须有 await

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

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

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

async def mygen(alist):

while len(alist) > 0:

c = randint(0, len(alist)-1)

yield alist.pop(c) # 单独的 async 与生成器是无法变成协程的,async 遇到生成器无法把函数变成协程

a = ["ss","dd","gg"]

c=mygen(a)

print(c)

可以看到输出

并不是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) # 非生成器函数,配合 asynic 成为协程,再配合 await 成为异步

strlist = ["ss","dd","gg"]

intlist=[1,2,5,6]

c1=mygen(strlist)

c2=mygen(intlist)

print(c1)

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

在上面的代码下面加上:

if __name__ == '__main__':

loop = asyncio.get_event_loop()

tasks = [ # 事件循环

c1,

c2

]

loop.run_until_complete(asyncio.wait(tasks))

print('All fib finished.')

loop.close()

就可以看到为追求高效率的交替执行的效果。

Python黑魔法 --- 异步IO( asyncio) 协程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值