Python 理解非平凡情况下生成器内部的StopIteration处理
我正在帮助维护一些现在包括自动Python 3.7测试的代码。这让我想到了一些与“生成器内部的更改迭代处理”相关的问题。我天真的理解是,可以使用try-except块修改旧代码,使之与所有python版本兼容,例如 旧代码:Python 理解非平凡情况下生成器内部的StopIteration处理,python,python-3.x,exception,generator,stopiteration,Python,Python 3.x,Exception,Generator,Stopiteration,我正在帮助维护一些现在包括自动Python 3.7测试的代码。这让我想到了一些与“生成器内部的更改迭代处理”相关的问题。我天真的理解是,可以使用try-except块修改旧代码,使之与所有python版本兼容,例如 旧代码: def f1(): it = iter([0]) while True: yield next(it) print(list(f1())) # [0] (in Py 3.6) # "RuntimeError: generator raise
def f1():
it = iter([0])
while True:
yield next(it)
print(list(f1()))
# [0] (in Py 3.6)
# "RuntimeError: generator raised StopIteration" (in Py 3.7;
# or using from __future__ import generator_stop)
变成:
def f2():
it = iter([0])
while True:
try:
yield next(it)
except StopIteration:
return
print(list(f2()))
# [0] (in all Python versions)
对于这个简单的例子,它是有效的,但我发现对于一些更复杂的代码,我正在重新分解它,但它不起作用。以下是Py 3.6的一个简单示例:
class A(list):
it = iter([0])
def __init__(self):
while True:
self.append(next(self.it))
class B(list):
it = iter([0])
def __init__(self):
while True:
try:
self.append(next(self.it))
except StopIteration:
raise
class C(list):
it = iter([0])
def __init__(self):
while True:
try:
self.append(next(self.it))
except StopIteration:
return # or 'break'
def wrapper(MyClass):
lst = MyClass()
for item in lst:
yield item
print(list(wrapper(A)))
# [] (wrong output)
print(list(wrapper(B)))
# [] (wrong output)
print(list(wrapper(C)))
# [0] (desired output)
我知道A
和B
示例完全相同,C
案例是与Python3.7兼容的正确方式(我还知道将重新分解为for
循环对于许多示例都是有意义的,包括这个人为设计的示例)
但问题是,为什么带有A
和B
的示例会生成一个空列表[]
,而不是[0]
?前两种情况在类的\uuuu init\uuuu
中有一个未捕获的停止迭代。在Python3.6中,list
构造函数可以很好地处理这个问题(根据版本的不同,可能会有一个警告)。但是,异常会在包装器
有机会迭代之前传播:实际失败的行是lst=MyClass()
,并且lst:
中的项的循环从未运行,导致生成器为空
在Python 3.6.4中运行此代码时,我在print
行(对于A
和B
)上都收到以下警告:
这里的结论有两方面:
不要让迭代器自行运行。你的工作是检查它何时停止。使用for
循环很容易做到这一点,但必须使用while
循环手动完成。案例A
就是一个很好的例子
不要重新引发内部异常。返回None
。案例B
不是一个好办法。中断
或返回
将在块中正常工作,但
块除外,就像您在C
中所做的那样
鉴于for
循环是C
中try-except块的语法糖,我通常建议使用它们,即使手动调用iter
:
class D(list):
it = iter([0])
def __init__(self):
for item in it:
self.append(item)
此版本在功能上等同于C
,并为您完成所有簿记工作。很少有情况需要实际的while
循环(我会跳过对next
的调用,但即使这些情况也可以用嵌套循环重写)。有趣的是,Python 3.6.6没有任何警告;但我认为你的解释基本上是正确的,谢谢!我想这就是为什么他们引入PEP来解释这些错误@克里斯·兰兹。这就是为什么即使手动调用iter
我仍然喜欢for
循环。它基本上就是选项C
中try-catch块的语法糖。节省了大量的输入,而且考虑到流程的性质,您很少需要其他任何东西。在这种情况下,我的真实示例不像我展示的那样做作,但总体上我同意“列表包装器在Python 3.6中处理得很好”——很清楚,不是列表包装器处理它,对吗?这仅仅是因为yield
将整个过程转化为生成器函数(但是您是正确的,生成器终止于lst=MyClass()
行),是吗?(如果没有yield
仍然会出现StopIteration
问题)
class D(list):
it = iter([0])
def __init__(self):
for item in it:
self.append(item)