一.yield or return 魔法

下面一道题需要在python3.3+ 环境下运行:

1
2
3
4
5
6
7
8
9
In [14]: def foo(n):
...: if n in (0, 1):
...: return [1]
...: for i in range(n):
...: yield i * 2

print(list(foo(0)))
print(list(foo(1)))
print(list(foo(2)))

请问分别输出什么?

这道题是我在flask作者写的:雾里看花之 Python Asyncio见到的。

输出结果分别是:[], [], [0,2], 为什么呢?刚才上面说需要在py3运行,如果在py2的话,那么就会直接报错:SyntaxError: 'return' with argument inside generator。那么为什么在python3中没有报错呢,这是因为从一个作为生成器的函数中 return 的值实际上引发了一个带有单个参数的 StopIteration,它不是由迭代器协议捕获的,而只是在协程代码中处理。py3中return和yield可以同时使用,可参考Return in generator together with yield in Python 3.3这个问题,就是说:

python3.3以后,在生成器中

  • return 相当于 raise StopIteration()
  • return xx 相当于 raise StopIteration(xx)

如果return有值的话,会有两种情况取得该值:

  1. try ,, catch后取exception对象的value属性
  2. yield from 代理

下面来实验下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 异常捕获方式
In [34]: try:
...: next(foo(1))
...: except StopIteration as ex:
...: print(ex)
...: print(ex.value)
...:
[1]
[1]

# yield from 方式
In [35]: def g():
...: x = yield from foo(1)
...: print(x)

In [37]: list(g())
[1]
Out[37]: []

现在我把上面的return改成yield, 如下又会输出什么呢?

1
2
3
4
5
6
7
8
9
In [10]: def foo(n):
...: if n in (0, 1):
...: yield [1]
...: for i in range(n):
...: yield i * 2
...:
print(list(foo(0)))
print(list(foo(1)))
print(list(foo(2)))

相关知识点: