协程系列(2) Python 的生成器和协程语法
发布网友
发布时间:2024-09-09 14:03
我来回答
共1个回答
热心网友
时间:2024-10-10 23:13
希望你已经阅读了上一篇文章,知道了什么是协程。在这篇文章中,我们将介绍Python的协程语法。
上期我们提到,一个可以暂停并且恢复的函数,就是一个协程。Python从3.5开始,通过PEP 492引入了新的语法,用于定义协程。这个语法是async def和await。但在之前,我想先介绍Python的生成器语法,因为生成器和协程有很多相似之处。
在函数中定义一个yield语句,就可以将一个函数变成生成器。
生成器是一种特殊的迭代器,它可以暂停并且恢复。生成器的定义和普通函数类似,只是在函数体中使用yield语句。
我们来看一个简单的例子:
g是一个生成器对象,它是通过调用simple_generator函数得到的。
生成器执行到yield语句时,会暂停,并且返回yield后面的值。
生成器对象有send方法,用于恢复生成器执行,这个参数会成为生成器中yield语句的返回值。
所以,第一次调用g.send(None),生成器恢复运行,直到第一个yield语句,返回123,被存入y中。
这时候继续调用g.send(1),生成器会继续执行。send的参数1会被存入x中。
生成器继续运行,直到第二个yield语句,返回456,存入y中。
再次调用g.send(2),生成器会继续执行,2被存入x中,运行到函数体结束。在最后,生成器会抛出StopIteration异常,表示生成器执行结束。
程序输出如下:
StopIteration异常被捕获,表示生成器执行结束。StopIteration异常的value属性,就是生成器的返回值。
PEP 492引入了async def和await语法,用于定义协程。它与生成器有很多相似之处。
await语句,它会暂停当前协程,并且等待coro协程执行结束。c是一个协程对象,它是通过调用simple_coro函数得到的。
协程对象有send方法,用于恢复协程执行,这个参数会成为await语句的返回值。
通过types.coroutine装饰器,可以将一个生成器函数变成一个协程函数。
第一步,调用c.send(None),协程恢复运行,直到await语句,等待coro1协程执行结束。
这时候进入coro协程,执行到yield语句,暂停,返回12345。这个值被存入y中。
程序输出为:
第二步,调用c.send(1),协程恢复运行,1被存入x1中,运行到函数体结束。在最后,协程会抛出StopIteration(123)异常,表示协程执行结束。
这时候StopIteration异常被捕获,123被存入x2中。
程序继续执行到return 456,然后抛出StopIteration(456)异常,表示协程执行结束。
这时候StopIteration异常被捕获,456被存入y中。
程序输出为:
在CPython实现中,生成器和协程是不同的东西,但是他们的实现非常相似。可以在CPython源代码中搜索PyGenObject是生成器的结构体,PyCoroObject是协程的结构体。
这两个对象的内部都持有完整的程序栈帧,也会记录字节码运行到哪一条,以及一些额外的状态信息。因为CPython实现中,要保存局部状态,必须保存完整的程序栈帧,由于其动态性,是无法预先知道需要哪些保存局部变量的。
所以话说回来,有栈协程和无栈协程在Python里更多还是靠语法和切换状态来区分。
CPython把await编译到相对应的字节码指令,遇到的时候就直接切进那个栈帧,然后执行那个栈帧,然后返回结果。这个过程和生成器的yield语句很像。
生成器和协程都是可以暂停并且恢复的函数。生成器通过yield语句实现,协程通过await语句实现。通过send方法,可以恢复生成器和协程的执行。
此外,生成器和协程还有throw和close方法,我略去了。有兴趣的可以在下面的链接中查看详细说明。
asyncio正如我们上一篇文章中所说的,是Python的协程库。它提供了一些高级的API,用于管理协程的执行。通过asyncio,可以很方便的编写异步程序。但没有asyncio,照样可以使用生成器和协程,编写异步程序。
在下一篇文章中,我们将介绍asyncio的关键组件,Future和Task。
Task和Future是asyncio的核心,也是asyncio的最小调度单元。