由于工做中慢慢开始用python的协程,因此想更好的理解一下实现方式,故翻译此文python
原文中把词汇表放到最后,可是我我的以为放在最开始比较好,这样能够增长当你看原文时的理解程度数据库
原生协程函数 Native coroutine function:express
由async def定义的协程函数,可使用await和return value语句编程
原生协程 Native coroutine:session
原生协程函数返回的对象。见“await表达式”一节。并发
基于生成器的协程函数 Generator-based coroutine function:app
基于生成器语法的协程,最多见的是用 @asyncio.coroutine装饰过的函数。框架
基于生成器的协程 Generator-based coroutine:异步
基于生成器的协程函数返回的对象。async
协程 Coroutine:
“原生协程”和“基于生成器的协程”都是协程。
协程对象 Coroutine object:
“原生协程对象”和“基于生成器的协程对象”都是协程对象。
Future-like对象 Future-like object:
一个有__await__方法的对象,或一个有tp_as_async->am_await函数的C语言对象,它们返回一个迭代器。Future-like对象能够在协程里被一条await语句消费(consume)。协程会被await语句挂起,直到await语句右边的Future-like对象的__await__执行完毕、返回结果。见“await表达式”一节。
Awaitable
一个Future-like对象或一个协程对象。见“await表达式”一节。
异步上下文管理器 Asynchronous context manager:
有__aenter__和__aexit__方法的对象,能够被async with语句使用。见“异步上下文管理器和‘async with’”一节。
可异步迭代对象 Asynchronous iterable:
有__aiter__方法的对象, 该方法返回一个异步迭代器对象。能够被async for语句使用。见“异步迭代器和‘async for’”一节。
异步迭代器 Asynchronous iterator:
有__anext__方法的对象。见“异步迭代器和‘async for’”一节。
随着互联网和链接程序的增加,引起了对响应性和可扩展代码的需求,该提议的目标是让咱们共容易的经过编写显示异步,高并发的python代码而且更加Pythonic
它提出把写成的概念独立出来,并引入新的支持语法。最终的目标是帮助在python中创建一个通用的,易于接近的异步编程构思模型,并使其尽量接近于同步编程(说白了就是让你经过相似写同步编程的方式,写出异步代码)
这个PEPE建设异步任务是相似于标准模块asyncio.events.AbstractEventLoop的事件循环调度和协调。虽然这个PEP不依赖人去特定的时间循环实现,但它仅仅与使用yield做为调度程序信号的协程类型相关,表示协程将等待知道事件(例如:IO)完成
咱们相信,这里提出的更改将有助于python在快速增加的异步编程领域保持更好的竞争力,由于许多其余语言已经采或将要采用相似的功能
对Python 3.5的初始beta版本的反馈致使从新设计支持此PEP的对象模型,以更清楚地将原生协程与生成器分离 - 而不是一种新的生成器,如今原生协程有明确的独立类型
这个改变主要是为了解决原生协程在tornado里使用出现的一些问题
在CPython3.5.2 中更新了__aiter__ 协议。
在3.5.2以前,__aiter__ 是被指望返回一个等待解析为异步迭代器,从3.5.2开始,__aiter__ 应该直接返回异步迭代器
若是在3.5.2中使用旧协议中,Python将引起PendingDeprecationWarning异常
在CPython 3.6中,旧的__aiter__协议仍将受到引起DeprecationWarning的支持
在CPython 3.7中,将再也不支持旧的__aiter__协议:若是__aiter__返回除异步迭代器以外的任何内容,则将引起RuntimeError。
当前的Python支持经过生成器(PEP342)实现协程,并经过PEP380中引入的yield from 语法进一步加强,这种方法有不少缺点:
这个PEP把协程从生成器独立出来,成为Python的一个原生事物。这会消除协程和生成器之间的混淆,方便编写不依赖特定库的协程代码。也为linter和IDE进行代码静态分析提供了机会。
使用原生协程和相应的新语法,咱们能够在异步编程时使用上下文管理器(context manager)和迭代器。以下文所示,新的async with语句能够在进入、离开运行上下文(runtime context)时进行异步调用,而async for语句能够在迭代时进行异步调用。
该提议引入了新的语法和语义来加强Python对协程支持。
请理解Python现有的协程(见PEP 342和PEP 380),此次改变的动机来自于asyncio框架(PEP 3156)和Confunctions提案(PEP 3152,此PEP已经被废弃)。
由此,在本文中,咱们使用“原生协程”指用新语法声明的协程。“生成器实现的协程”指用传统方法实现的协程。“协程”则用在两个均可以使用的地方。
使用如下语法声明原生协程:
async def read_data(db): pass
协程语法的关键点:
types模块添加了一个新函数coroutine(fn),使用它,“生成器实现的协程”和“原生协程”之间能够进行互操做。
@types.coroutine def process_data(db): data = yield from read_data(db) ...
该函数将CO_ITERABLE_COROUTINE标志应用于生成器函数的代码对象,使其返回一个协程对象。若是fn不是生成器函数,它将被包装。若是它返回一个生成器,它将被包装在一个等待的代理对象中(参见下面的等待对象的定义)。
types.coroutine()不会设置CO_COROUTINE标识,只有用新语法定义的原生协程才会有这个标识。
新的await表达式用于得到协程执行的结果:
async def read_data(db): data = await db.fetch('SELECT ...') ...
await 和yield from 是很是相似的,会挂起read_data的执行,直到等待db.fetch完成并返回结果数据。
await使用yield from的实现,可是加入了一个额外步骤——验证它的参数类型。await只接受awaitable对象,awaitable对象是如下的其中一个:
例如,在asyncio模块,要想在await语句里使用Future对象,惟一的修改是给asyncio.Future加一行:__await__ = __iter__
在本文中,有__await__方法的对象被称为Future-like对象(协程会被await语句挂起,直到await语句右边的Future-like对象的__await__执行完毕、返回结果。)
若是__await__返回的不是一个迭代器,则引起TypeError异常。
在CPython C API,有tp_as_async.am_await函数的对象,该函数返回一个迭代器(相似__await__方法)
若是在async def函数以外使用await语句,会引起SyntaxError异常。这和在def函数以外使用yield语句同样。
若是await右边不是一个awaitable对象,会引起TypeError异常。
有效的语法示例
Expression | Will be parsed as |
---|---|
if await fut: pass | if (await fut): pass |
if await fut + 1: pass | if (await fut) + 1: pass |
pair = await fut, 'spam' | pair = (await fut), 'spam' |
with await fut, open(): pass | with (await fut), open(): pass |
await foo()['spam'].baz()() | await ( foo()['spam'].baz()() ) |
return await coro() | return ( await coro() ) |
res = await coro() ** 2 | res = (await coro()) ** 2 |
func(a1=await coro(), a2=0) | func(a1=(await coro()), a2=0) |
await foo() + await bar() | (await foo()) + (await bar()) |
-await foo() | -(await foo()) |
无效的用法
Expression | Should be written as |
---|---|
await await coro() | await (await coro()) |
await -coro() | await (-coro()) |
异步上下文管理器(asynchronous context manager),能够在它的enter和exit方法里挂起、调用异步代码。
为此,咱们设计了一套方案,添加了两个新的魔术方法:__aenter__和__aexit__,它们必须返回一个awaitable。
异步上下文管理器的一个示例:
class AsyncContextManager: async def __aenter__(self): await log('entering context') async def __aexit__(self, exc_type, exc, tb): await log('exiting context')
采纳了一个异步上下文管理器的新语法
async with EXPR as VAR:
BLOCK
这在语义上等同于:
mgr = (EXPR) aexit = type(mgr).__aexit__ aenter = type(mgr).__aenter__(mgr) VAR = await aenter try: BLOCK except: if not await aexit(mgr, *sys.exc_info()): raise else: await aexit(mgr, None, None, None)
与常规with语句同样,能够在单个async with语句中指定多个上下文管理器。
在使用async with时,若是上下文管理器没有__aenter__和__aexit__方法,则会引起错误。在async def函数以外使用async with则会引起SyntaxError异常。
使用异步上下文管理器,能够轻松地为协同程序实现适当的数据库事务管理器:
async def commit(session, data): ... async with session.transaction(): ... await session.update(data) ...
加锁的处理也更加简洁
async with lock:
...
而再也不是:
with (yield from lock): ...
异步迭代器能够在它的iter实现里挂起、调用异步代码,也能够在它的__next__方法里挂起、调用异步代码。要支持异步迭代,须要:
一个一步迭代的例子:
class AsyncIterable: def __aiter__(self): return self async def __anext__(self): data = await self.fetch_data() if data: return data else: raise StopAsyncIteration async def fetch_data(self): ...
采纳了一个迭代异步迭代器的新语法:
async for TARGET in ITER: BLOCK else: BLOCK2
在语义上等同于:
iter = (ITER) iter = type(iter).__aiter__(iter) running = True while running: try: TARGET = await type(iter).__anext__(iter) except StopAsyncIteration: running = False else: BLOCK else: BLOCK2
若是async for的迭代器不支持__aiter__方法,则引起TypeError异常。若是在async def函数外使用async for,则引起SyntaxError异常。
和普通的for语句同样,async for有一个可选的else分句。
使用异步迭代协议,能够在迭代期间异步缓冲数据:
async for data in cursor: ...
其中cursor是一个异步迭代器,它在每N次迭代后从数据库中预取N行数据。
如下代码说明了新的异步迭代协议:
class Cursor: def __init__(self): self.buffer = collections.deque() async def _prefetch(self): ... def __aiter__(self): return self async def __anext__(self): if not self.buffer: self.buffer = await self._prefetch() if not self.buffer: raise StopAsyncIteration return self.buffer.popleft()
而后,能够这样使用Cursor类
async for row in Cursor(): print(row)
与下述代码相同:
i = await Cursor().__aiter__() while True: try: row = await i.__anext__() except StopAsyncIteration: break else: print(row)
如下是将常规迭代转换为异步迭代的实用程序类。虽然这不是一件很是有用的事情,但代码说明了常规迭代器和异步迭代器之间的关系。
class AsyncIteratorWrapper: def __init__(self, obj): self._it = iter(obj) def __aiter__(self): return self async def __anext__(self): try: value = next(self._it) except StopIteration: raise StopAsyncIteration return value async for letter in AsyncIteratorWrapper("abc"): print(letter)
协程在内部仍然是基于生成器实现的,所以,在PEP479以前,下面二者是没有区别的
def g1(): yield from fut return 'spam'
和
def g2(): yield from fut raise StopIteration('spam')
因为PEP 479已被正式采纳,并做用于协程,如下代码的StopIteration会被包装(wrapp)成一个RuntimeError。
async def a1(): await fut raise StopIteration('spam')
因此,要想通知外部代码迭代已经结束,抛出一个StopIteration异常的方法不行了。所以,添加了一个新的内置异常StopAsyncIteration,用于表示迭代结束。
此外,根据PEP 479,协程抛出的全部StopIteration异常都会被包装成RuntimeError异常。
本节仅适用于具备CO_COROUTINE的原生协程,即便用新的async def 定义的函数
对于asyncio模块里现有的“基于生成器的协程”,仍然保持不变。
为了把协程和生成器的概念区分开来:
协程是基于生成器实现的,所以它们有共同的代码。像生成器对象那样,协程也有throw(),send()和close()方法。
对于协程,StopIteration和GeneratorExit起着一样的做用(虽然PEP 479已经应用于协程)。详见PEP 34二、PEP 380,以及Python文档。
对于协程,send(),throw()方法用于往Future-like对象发送内容、抛出异常。
初级开发者在使用协程时可能忘记使用yield from语句,好比:
@asyncio.coroutine def useful(): asyncio.sleep(1) # this will do nothing without 'yield from'
为了调试这种错误,在asyncio中有一个特殊的调试模式,其中@coroutine装饰器用一个特殊对象包装全部函数,并使用析构函数记录警告。每当一个包装的生成器被垃圾回收时,就会生成一条详细的日志消息,其中包含有关定义装饰器函数的确切位置,堆栈跟踪收集位置等的信息.Wrapper对象还提供了一个方便的__repr__函数,其中包含有关生成器的详细信息。
为了更好地与现有框架(如Tornado,见[13])和编译器(如Cython,见[16])集成,增长了两个新的抽象基类(ABC):
注意,“基于生成器的协程”(有CO_ITERABLE_COROUTINE标识)并不实现__await__方法,所以它们不是collections.abc.Coroutine和collections.abc.Awaitable的实例:
@types.coroutine def gencoro(): yield assert not isinstance(gencoro(), collections.abc.Coroutine) # however: assert inspect.isawaitable(gencoro())
为了便于测试对象是否支持异步迭代,还添加了两个ABC: