StopIteration 何时会转换为 RuntimeError?
When will a StopIteration be converted into RuntimeError?
我正在阅读 Python 3 here:
的文档
If a generator code directly or indirectly raises StopIteration
, it is converted into a RuntimeError
(retaining the StopIteration
as the new exception's cause).
我不明白,谁能解释一下?
这是我在 Python 3.6 中尝试过的方法,但似乎没有发现任何问题:
def gen1():
yield from [1, 2, 3]
raise StopIteration
def gen2():
raise StopIteration
try:
a = list(gen1())
# a == [1, 2, 3]
except RuntimeError:
print("Caught")
try:
a = gen1()
next(a), next(a), next(a), next(a), next(a)
except RuntimeError:
print("Caught")
try:
gen2()
except RuntimeError:
print("Caught")
try:
a = list(gen2())
except RuntimeError:
print("Caught")
特别是,对 gen2()
的两次调用都引发了 StopIteration
,但仍未转换为 RuntimeError
。
您没有注意到此更改适用于 Python 3.7 及更高版本。 您不会在 Python 3.6 或更早版本 中看到转换,除非您先通过 from __future__
导入启用该功能(自 Python 起可用) 3.5).
来自您链接的同一页面:
Changed in version 3.5: Introduced the RuntimeError
transformation via from __future__ import generator_stop
, see PEP 479.
Changed in version 3.7: Enable PEP 479 for all code by default: a StopIteration
error raised in a generator is transformed into a RuntimeError
.
PEP 479 -- Change StopIteration handling inside generators 进一步详细说明了进行此更改的原因及其应用方式。对于您的代码,运行 on Python 3.7,输出变为:
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=0, releaselevel='final', serial=0)
>>> def gen1():
... yield from [1, 2, 3]
... raise StopIteration
...
>>> def gen2():
... yield 42 # make this an actual generator
... raise StopIteration
...
>>> try:
... a = list(gen1())
... except RuntimeError:
... print("Caught")
...
Caught
>>> try:
... a = gen1()
... next(a), next(a), next(a), next(a), next(a)
... except RuntimeError:
... print("Caught")
...
Caught
>>> try:
... a = list(gen2())
... except RuntimeError:
... print("Caught")
...
Caught
请注意,我在 gen2()
中添加了 yield 42
行以使其成为生成器。如果正文中没有 yield
或 yield from
,您将获得一个常规函数。调用一个生成器函数会产生一个生成器对象并且函数主体开始暂停,而调用一个普通函数会立即执行主体:
>>> def normal():
... raise StopIteration
...
>>> def generator():
... raise StopIteration
... yield # never reached, but this is now a generator
...
>>> normal()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in normal
StopIteration
>>> generator()
<generator object generator at 0x105831ed0>
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 2, in generator
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration
对于 Python 3.6,您将使用 from __future__ import generator_stop
编译器开关(在编写脚本或模块时在代码顶部使用它):
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0)
>>> def generator():
... raise StopIteration
... yield
...
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in generator
StopIteration
>>> from __future__ import generator_stop
>>> def generator(): # re-define it so it is compiled anew
... raise StopIteration
... yield
...
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 2, in generator
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration
我正在阅读 Python 3 here:
的文档If a generator code directly or indirectly raises
StopIteration
, it is converted into aRuntimeError
(retaining theStopIteration
as the new exception's cause).
我不明白,谁能解释一下?
这是我在 Python 3.6 中尝试过的方法,但似乎没有发现任何问题:
def gen1():
yield from [1, 2, 3]
raise StopIteration
def gen2():
raise StopIteration
try:
a = list(gen1())
# a == [1, 2, 3]
except RuntimeError:
print("Caught")
try:
a = gen1()
next(a), next(a), next(a), next(a), next(a)
except RuntimeError:
print("Caught")
try:
gen2()
except RuntimeError:
print("Caught")
try:
a = list(gen2())
except RuntimeError:
print("Caught")
特别是,对 gen2()
的两次调用都引发了 StopIteration
,但仍未转换为 RuntimeError
。
您没有注意到此更改适用于 Python 3.7 及更高版本。 您不会在 Python 3.6 或更早版本 中看到转换,除非您先通过 from __future__
导入启用该功能(自 Python 起可用) 3.5).
来自您链接的同一页面:
Changed in version 3.5: Introduced the
RuntimeError
transformation viafrom __future__ import generator_stop
, see PEP 479.Changed in version 3.7: Enable PEP 479 for all code by default: a
StopIteration
error raised in a generator is transformed into aRuntimeError
.
PEP 479 -- Change StopIteration handling inside generators 进一步详细说明了进行此更改的原因及其应用方式。对于您的代码,运行 on Python 3.7,输出变为:
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=0, releaselevel='final', serial=0)
>>> def gen1():
... yield from [1, 2, 3]
... raise StopIteration
...
>>> def gen2():
... yield 42 # make this an actual generator
... raise StopIteration
...
>>> try:
... a = list(gen1())
... except RuntimeError:
... print("Caught")
...
Caught
>>> try:
... a = gen1()
... next(a), next(a), next(a), next(a), next(a)
... except RuntimeError:
... print("Caught")
...
Caught
>>> try:
... a = list(gen2())
... except RuntimeError:
... print("Caught")
...
Caught
请注意,我在 gen2()
中添加了 yield 42
行以使其成为生成器。如果正文中没有 yield
或 yield from
,您将获得一个常规函数。调用一个生成器函数会产生一个生成器对象并且函数主体开始暂停,而调用一个普通函数会立即执行主体:
>>> def normal():
... raise StopIteration
...
>>> def generator():
... raise StopIteration
... yield # never reached, but this is now a generator
...
>>> normal()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in normal
StopIteration
>>> generator()
<generator object generator at 0x105831ed0>
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 2, in generator
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration
对于 Python 3.6,您将使用 from __future__ import generator_stop
编译器开关(在编写脚本或模块时在代码顶部使用它):
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0)
>>> def generator():
... raise StopIteration
... yield
...
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in generator
StopIteration
>>> from __future__ import generator_stop
>>> def generator(): # re-define it so it is compiled anew
... raise StopIteration
... yield
...
>>> next(generator())
Traceback (most recent call last):
File "<stdin>", line 2, in generator
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration