BeginMan blog

wtfpython 第三弹

一.yield or return 魔法

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

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 代理

下面来实验下:

# 异常捕获方式
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, 如下又会输出什么呢?

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)))

相关知识点: