2.3.1 Python的Generator

Python的Generator是一个典型的无栈协程的实现。可以在任意Python函数中调用yield来实现当前函数调用的挂起,yield的参数作为对下一次next(gen)调用的返回值,如代码清单2-2所示。

代码清单2-2 Python Generator使用示例


import time

def numbers():
  i = 0
  while True:
    yield(i) //... ①
    i += 1
    time.sleep(1)

gen = numbers()

print(f"[0] {next(gen)}") //... ②
print(f"[1] {next(gen)}") //... ③

for i in gen: //... ④
  print(f"[Loop] {i}")

运行代码清单2-2时,首先会在①处yield,并将0传出,在②处输出:


[0] 0

接着自③处调用next,将调度权从主流程转移到numbers函数当中,从上一次挂起的位置①处继续执行。i的值修改为1,1s后,再次通过yield(1)挂起,在③处输出:


[1] 1

之后,以同样的逻辑在for循环中一直输出[Loop]n,直到程序被终止。Generator的状态转移如图2-2所示。

我们可以看到,之所以称Python的Generator为协程,就是因为它可以通过yield来挂起当前Generator函数的执行,通过next来恢复参数对应的Generator执行,从而实现挂起、恢复的协程调度权控制转移。

当然,如果在numbers函数中嵌套调用yield,就无法对numbers的调用进行挂起了,如代码清单2-3所示。

代码清单2-3 Python Generator不支持嵌套


def numbers():
  i = 0
  while True:
    yield_here(i) //... ①
    i += 1
    time.sleep(1)

def yield_here(i):
  yield(i)

图2-2 Generator的状态转移示意图

这时候我们再调用numbers函数,就会陷入死循环而无法返回,因为这次yield_here的返回值才是Generator,循环里一直创建新的Generator而没有返回给外部。由此可见,Python的Generator属于非对称无栈协程的一种实现。

延伸 Python从3.5版开始支持async/await,原理与前面讲到JavaScript的实现类似,与Generator的不同之处在于可以通过这一组关键字实现在函数的嵌套调用中挂起。