我正在帮助维护一些代码,其中包括自动Python 3.7测试。这导致我遇到一些与PEP 479“更改生成器内部的StopIteration处理”有关的问题。我天真的理解是,您可以使用try-except块来修改旧代码,使其与所有python版本兼容,例如
旧代码:
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
的情况是与Python 3.7兼容的正确方式(我也知道重构为{ {1}}循环对于许多示例来说都是有意义的,包括这个人为设计的示例。
但是问题是,为什么带有for
和A
的示例会产生一个空列表B
而不是[]
?
答案 0 :(得分:3)
前两个案例在班级的StopIteration
中引发了一个未被捕获的__init__
。 list
构造函数可以在Python 3.6中很好地处理此问题(可能会出现警告,具体取决于版本)。但是,异常在之前传播 wrapper
才有机会进行迭代:有效失败的行是lst = MyClass()
,而循环for item in lst:
从未运行,从而导致生成器为空。
当我在Python 3.6.4中运行此代码时,在print
的两行(对于A
和B
)上都收到以下警告:
DeprecationWarning: generator 'wrapper' raised StopIteration
这里的结论是双重的:
for
循环很容易做到这一点,但是必须使用while
循环手动完成。案例A
是一个很好的例子。None
。案例B
并不是解决之道。与在break
中所做的一样,return
或except
在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
的调用是我想到的情况,但即使是那些情况也可以使用嵌套循环来重写)。