Coroutines
asyncio在3.4版本添加到Python中,但通过
async def
和await
关键字创建coroutines的语法是3.5才加入的,在这之前,人们把generators当作coroutines来使用,在一些老的语法中,你可以看到用@asyncio.coroutine
之类的装饰器加yield
的句式,在本文中,请摒弃过去的写法。
async def
关键字
>>> async def f(): # 1
... return 123
>>> type(f) # 2
<class 'function'>
>>> import inspect # 3
>>> inspect.iscoroutinefunction(f)
True
- 这是声明coroutine的最简单方式;
- 与生成器函数的判定一样,coroutine也会被判定为协程函数,只有在调用这个函数时才能获得协程,就如生成器一样;
- 标准库中的检查模块可以提供比type方法更好的内省能力。
正是由于在3.4版本用生成器当作协程来使用,所以在3.5版本中,async def
的用法以及效果与生成器几乎相同。我们通过代码来观察Python如何在不同协程中切换,首先先看下如何获得return的值。
当coroutine return的时候,将会抛出一个StopIteration
异常。
>>> async def f():
... return 123
>>> coro = f()
>>> try:
... coro.send(None) # 1
... except StopIteration as e:
... print('The answer was: ', e.value) # 2
The answer was: 123
- coroutine通过发送None来启动,这就是loop在背后做的事;
- 协程返回时抛出StopIteration异常,可以通过异常的value属性访问返回值。
协程的开始和结束是通过send()和StopIteration来定义的,loop负责在背后做这些事,开发者只需要为loop安排task即可。
await
关键字
await
关键字总是接收一个参数,其类型必须是awaitable的,必须是如下两种之一:
1. 一个coroutine;
2. 一个实现了__await__()
方法的对象,这个方法必须返回一个迭代器。
async def f():
await asyncio.sleep(1.0)
return 123
async def main():
result = await f() # 1
return result
- 这里调用了f函数返回了一个coroutine。
在开始学习loop之前,了解一下如何向协程提供异常很有用,异常通常用于取消协程,在task.cancel()
时,loop在内部使用coro.throw()
来抛出一个asyncio.CancelledError
。
>>> coro = f() # 使用上面的协程
>>> coro.send(None)
<Future pending>
>>> coro.throw(Exception, 'hello') # 通过throw方法提供一个异常和值,该异常将在await处产生
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
Exception: hello
>>> async def f():
... try:
... while True: await asyncio.sleep(0)
... except asyncio.CancelledError:
... print('I was cancelled!')
... else:
... return 111
>>> coro = f()
>>> coro.send(None)
>>> coro.send(None)
>>> coro.throw(asyncio.CancelledError)
I was cancelled!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration # 正是由于内部捕捉了CancelledError,这个协程得以正常退出
>>> import asyncio
>>> async def f():
... try:
... while True: await asyncio.sleep(0)
... except asyncio.CancelledError:
... print('Cancelled')
... while True: await asyncio.sleep(0) # 跳转到另一个协程上去了
... else: return 1
>>> coro = f()
>>> coro.send(None)
>>> coro.throw(asyncio.CancelledError)
Cancelled
>>> coro.send(None)
目前为止的代码中,都是通过手动调用send(None)
和throw(asyncio.CancelledError)
来模拟loop的,下一章开始学习用loop来自动处理。
>>> async def f():
... await asyncio.sleep(0)
... return 123
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(f())
123