我在大概一年前写了 一篇东西 讨论 Python 3 中的 yield from 语句,顺便还 吐槽了一下 Python 对 coroutine 和 generator 不作区分的做法。最近在 StackOverflow 潜水的时候看到 有位同学问到 Python 3.5 里 yield from 语法的兼容性,才发 现 Python 已经要放弃 yield from 语法了……
PEP 492
所谓“放弃 yield from 语法”的决定是由 PEP 492 提出的。这个 PEP 的 Rationale 里列举了这么几个原因:
- Coroutine 和 generator 太TM容易搞混了;
- 用 yield from 语句作为异步函数的标识不够明显,来个重构什么的把 yield from 移到别的地方去就会导致程序逻辑出错;
- 只有在语法上容许 yield from 语句的地方才能调用异步函数,像 with 语句、for 语句之类的就只能进行同步调用了。
我觉得吧,最重要的还是第一个原因,想当年作为新手的时候真的是被折腾得够呛; 至于后面两个原因则是属于“可以有”的范畴,因为 with 语句和 for 语句本身就有 点语法糖的味道。
所以,简短概括的话, PEP 原文是这样说的:
It is proposed to make coroutines a proper standalone concept in Python, and introduce new supporting syntax.
可见此 PEP 的主要目的就是将 coroutine 和 generator 完全独立开来,里面提出 的所有其他语法修改都是为这个目的服务的。
新类型
PEP 492 引入了新的原生(native) coroutine 类型。调用异步函数后返回的不再 是 generator ,而是 coroutine. 这个类型没有实现 generator 的任何接口,所以 把 coroutine 当 generator 用的话 Python 会报错。
为了向后兼容,原本基于 generator 的 “非原生” coroutine 也可以正常工作,但 是原生 coroutine 和非原生 coroutine 不能混用 ,也就是说,在原生 coroutine 里不能使用 yield from 语句,而在非原生 coroutine 里则不能 yield from 一个原生 coroutine.
这个意思很清楚:在一个异步程序里,要么只用新的异步函数,要么就只用旧的,新旧 一起用是万恶之源我们必欲除之而后快。
新语法
与原生 coroutine 对应的语法是这样的:
1 2 3 4 5 6 | async def read_data(db): pass async def process_data(db): data = await read_data(db) # ... process data ... |
可见引入了 async 和 await 这两个关键字——而且这不正是 Nim 的语法咩……既然都有新 关键字了,就不用白不用; async 关键字还可以用在 with 和 for 语句中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class AsyncResource: async def __aenter__(self): return (await fetch_resource()) async def __aexit__(self, exc_type, exc, tb): await release_resource() async with AsyncResource() as r: # ... do things with r ... class AsyncIterable: async def __aiter__(self): return self async def __anext__(self): data = await fetch_data() if data: return data else: raise StopAsyncIteration async for data in AsyncIterable(): # ... process data ... |
过渡方法
当然和当年 Python 2 过渡到 Python 3 政策一样:新代码用新语法,旧代码就一 直用旧语法好了。不过就像 SO 上某人的评论一样: yield from 才出现没多久就 变成 “deprecated” 了,想想有点搞笑啊……