我在大概一年前写了 一篇东西 讨论 Python 3 中的 yield from 语句,顺便还 吐槽了一下 Python 对 coroutine 和 generator 不作区分的做法。最近在 StackOverflow 潜水的时候看到 有位同学问到 Python 3.5 里 yield from 语法的兼容性,才发 现 Python 已经要放弃 yield from 语法了……

PEP 492

所谓“放弃 yield from 语法”的决定是由 PEP 492 提出的。这个 PEP 的 Rationale 里列举了这么几个原因:

  1. Coroutine 和 generator 太TM容易搞混了;
  2. 用 yield from 语句作为异步函数的标识不够明显,来个重构什么的把 yield from 移到别的地方去就会导致程序逻辑出错;
  3. 只有在语法上容许 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” 了,想想有点搞笑啊……