Python中的yield可以做什么

python | Comments

第一次了解yield是通过Stackoverflow上的这个问题What does the “yield” keyword do in Python?。这个答案前后看了几次,后来才开始在自己的代码里实现一些简单的generator。不过还没用它来实现过协程之类的功能,Python3.5中才引入的async/await,就更没有仔细了解过,最近又来了兴趣,就梳理一下Python中和yield相关的知识。

Simple Generators

Python2.2的时候, PEP255引入了yield语句来实现generator,有了generator就能很方便的实现一个iterator,不需要每次都在内存中创建一个完整的列表,既能节省内存,也可以用来实现无限的数组。比如下面两个例子:

1
2
3
4
5
def range(up_limit):
    index = 0
    while index < up_limit:
        yield index
        index += 1

计算斐波那契数列

1
2
3
4
5
def fib():
    a, b = 0, 1
    while 1:
        yield b
        a, b = b, a+b

Coroutines

根据Wikipedia上的定义,"Coroutine are computer program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.“。

简单翻译一下就是,协程是一种用来实现非抢占式多任务的泛化子过程,它允许有多个入口,可以在不同的地方挂起和恢复执行。更简单直接点的说法:协程就是你可以随时暂停的函数。

某种程度上来说Python中的generator已经算是一个协程,可以在我们想要的地方暂停和恢复执行,但是generator只能输出一个值,不能从外面接受输入,而且只能把控制权返回给它的调用者,无法控制yield之后的执行顺序。为了实现完整的协程,在Python2.5的时候PEP342yield引入了一些增强的功能,实现了完整的协程。

其中最重要的一个改动就是把yield从语句(statement)改成了表达式(expression),也就是说yield可以出现在等号的右边,可以返回一个值了。新增的send方法可以在恢复generator的时候传递一个值回去。

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
def grep(pattern):
    while 1:
        line = yield
        if pattern in line:
            print(line)
> g = grep('Python')
> next(g)
> g.send("Go")
> g.send("Python")
Python

Subgenerator

yield变成表达式之后,Python3.3的时候,PEP380接着又引入了yield from,可以让我们从一个generator代理到另一个subgenerator。也就是可以自己指定yield到哪,而不只是返回到调用者。什么意思呢?举个简单的例子。假设我们需要一个grep_python的函数,没有yield from我们要怎么实现呢?

1
2
3
4
5
def grep_python():
    g = grep("Python")
    next(g)
    line = yield
    g.send(line)

有了yield from之后就可以这么写:

1
2
def grep_python():
    yield from grep("Python")

asyncio & async/await

到了Python3.4, PEP3156在Python的标准库中添加了一个Event loop实现。Python中coroutine重要性已经越来越明显,但是基于yieldyield from实现的coroutine总会让我们把它和普通的generator弄混。为了彻底区分开这两个概念,Python3.5中引入了async/await来创建和控制coroutine。async用来创建一个coroutine,await相当于yield from,用来挂起当前coroutine,直到可以获取结果。 新的代码看起来更简洁,就像这样。

1
2
async def read_data(db):
    data = await db.fetch('SELECT ...')

看样子在Python中写异步并发的代码已经越来越方便,现在唯一的问题是asyncioasync/await都还很新,没有多少库可以用。而且理解起来也没有Go里面的goroutine来的简单直接。好在Python胜在语法简洁,调试方便。虽然没有goroutine那样的先天优势, 还是觉得写Python的代码会比较舒服,希望以后Python在并发编程方面也能占得一席之地。

Comments