了解非平凡情况下生成器内部的StopIteration处理

时间:2018-10-29 13:26:30

标签: python python-3.x exception generator stopiteration

我正在帮助维护一些代码,其中包括自动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)

我知道AB的例子是完全等效的,并且C的情况是与Python 3.7兼容的正确方式(我也知道重构为{ {1}}循环对于许多示例来说都是有意义的,包括这个人为设计的示例。

但是问题是,为什么带有forA的示例会产生一个空列表B而不是[]

1 个答案:

答案 0 :(得分:3)

前两个案例在班级的StopIteration中引发了一个未被捕获的__init__list构造函数可以在Python 3.6中很好地处理此问题(可能会出现警告,具体取决于版本)。但是,异常在之前传播 wrapper才有机会进行迭代:有效失败的行是lst = MyClass(),而循环for item in lst:从未运行,从而导致生成器为空。

当我在Python 3.6.4中运行此代码时,在print的两行(对于AB)上都收到以下警告:

DeprecationWarning: generator 'wrapper' raised StopIteration

这里的结论是双重的:

  1. 不要让迭代器自行耗尽。检查何时停止是您的工作。使用for循环很容易做到这一点,但是必须使用while循环手动完成。案例A是一个很好的例子。
  2. 请勿重新引发内部异常。返回None。案例B并不是解决之道。与在break中所做的一样,returnexceptC块中可以正常工作。

鉴于for循环是C中try-except块的语法糖,我通常建议使用它们,即使手动调用iter也是如此:

class D(list):
    it = iter([0])
    def __init__(self):
        for item in it:
            self.append(item)

此版本在功能上等同于C,并为您完成所有簿记工作。很少有需要实际的while循环的情况(跳过对next的调用是我想到的情况,但即使是那些情况也可以使用嵌套循环来重写)。