为什么耗尽的发电机不止一次提高StopIteration?

时间:2016-09-21 16:50:43

标签: python python-3.x generator

为什么当多次调用耗尽的生成器时,每次都会引发StopIteration,而不是仅仅是第一次尝试?后续调用是否毫无意义,并指出调用者代码中可能存在错误?

def gen_func():
    yield 1
    yield 2
gen = gen_func()
next(gen)
next(gen)
next(gen) # StopIteration as expected
next(gen) # why StopIteration and not something to warn me that I'm doing something wrong

当有人意外使用过期的生成器时,这也会导致此行为:

def do_work(gen):
    for x in gen:
        # do stuff with x
        pass

    # here I forgot that I already used up gen
    # so the loop does nothing without raising any exception or warning
    for x in gen:
        # do stuff with x
        pass

def gen_func():
    yield 1
    yield 2

gen = gen_func()
do_work(gen)

如果第二次和以后尝试调用耗尽的生成器引发了一个不同的异常,那么捕获这种类型的错误会更容易。

也许有一个重要的用例是多次调用耗尽的生成器并获得StopIteration

3 个答案:

答案 0 :(得分:4)

它是迭代协议的一部分:

  

一旦迭代器的__next__()方法引发StopIteration,它就必须   在接下来的电话中继续这样做。没有的实现   服从此财产被视为破产。

来源:https://docs.python.org/3/library/stdtypes.html#iterator-types

答案 1 :(得分:3)

  

也许有一个重要的用例是多次调用耗尽的生成器并获得StopIteration

具体来说,当您想在同一个迭代器上执行多个循环时。以下是依赖于此行为的itertools文档中的示例:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

答案 2 :(得分:0)

这是包装器的一种实现,只要多次停止StopIteration,它就会引发错误,因为已经noted by VPfB,这被认为是坏的

#!/usr/bin/env python3.8
from typing import TypeVar, Iterator

"""
https://docs.python.org/3/library/stdtypes.html#iterator-types
This is considered broken by the iterator protocol, god knows why
"""

class IteratorExhaustedError(Exception):
    """Exception raised when exhausted iterators are ``next``d"""

T = TypeVar("T")
class reuse_guard(Iterator[T]):
    """
    Wraps an iterator so that StopIteration is only raised once,
    after that, ``IteratorExhaustedError`` will be raised to detect
    fixed-size iterator misuses
    """
    def __init__(self, iterator: Iterator[T]):
        self._iterated: bool = False
        self._iterator = iterator

    def __next__(self) -> T:
        try:
            return next(self._iterator)
        except StopIteration as e:
            if self._iterated:
                raise IteratorExhaustedError(
                    "This iterator has already reached its end")
            self._iterated = True
            raise e
    
    def __iter__(self) -> Iterator[T]:
        return self

示例:

In [48]: iterator = reuse_guard(iter((1, 2, 3, 4)))

In [49]: list(iterator)
Out[49]: [1, 2, 3, 4]

In [50]: list(iterator)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-47-456650faec86> in __next__(self)
     19         try:
---> 20             return next(self._iterator)
     21         except StopIteration as e:

StopIteration:

During handling of the above exception, another exception occurred:

IteratorExhaustedError                    Traceback (most recent call last)
<ipython-input-50-5070d0fe4365> in <module>
----> 1 list(iterator)

<ipython-input-47-456650faec86> in __next__(self)
     21         except StopIteration as e:
     22             if self._iterated:
---> 23                 raise IteratorExhaustedError(
     24                     "This iterator has already reached its end")
     25             self._iterated = True

IteratorExhaustedError: This iterator has already reached its end

编辑: 在回顾有关迭代器协议的文档之后,在我看来,表明不再继续提高StopIteration的迭代器应视为已被破坏的目的更多地是针对 yield values 而不是引发异常的迭代器,在这种情况下,可以更清楚地知道,迭代器一旦用尽,就不应使用。这只是我的解释思想。