在使用 map(next, iterables) 的 StopIteration 之前引发 ValueError

ValueError raised before StopIteration with map(next, iterables)

我正在像这样在 4 个列表迭代器上编写一个循环:

it0 = iter(['foo','bar'])
it1 = iter(['bar','foo'])
it2 = iter(['foo','foo'])
it3 = iter(['bar','bar'])

try:
    a, b, c, d = map(next, (it0, it1, it2, it3))
    while True:
        #some operations that can raise ValueError
        if a+b+c+d == 'foobar'*2:
            a, b, c, d = map(next, (it0, it1, it2, it3))
        elif a+b == 'foobar':
            c, d = map(next,(it2, it3))
        else:
            a, b = map(next,(it0, it1))
except StopIteration:
    pass

但是当我在 Python3 中 运行 这段代码时,我在预期的 StopIteration 之前得到了 ValueError: not enough values to unpack (expected 2, got 0)

我不想在 except 语句中捕获 ValueError 因为 while 循环中的某些操作也可能导致 ValueError 应该停止程序。

为什么 next 没有在赋值前抛出异常?

在Python2.7中,StopIteration先提出。

StopIteration 被引发,但是元组赋值吞没了它,因为它将 StopIteration 视为来自 map() 迭代器的信号 it 已完成生成值:

>>> i0, i1 = iter(['foo']), iter([])
>>> m = map(next, (i0, i1))
>>> next(m)
'foo'
>>> next(m)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> i0, i1 = iter(['foo']), iter([])
>>> m = map(next, (i0, i1))
>>> a, b = m
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 2, got 1)

这是正常行为; Python 期望迭代器作为输入的内置函数始终使用 StopIteration 作为 'iteration is done' 信号,并且元组解包必须在此处迭代。

首先将 map() 输出转换为列表并测试长度,在每个可迭代对象上分别使用 next() 而不是使用 map(),或者在本地捕获 ValueError .

测试长度将不得不重新提高StopIteration:

values = list(map(next, (it0, it1, it2, it3)))
if len(values) < 4:
    raise StopIteration
a, b, c, d = values

注意这里list()吞掉了StopIteration异常

map() 操作捕获 ValueError

try:    
    a, b, c, d = map(next, (it0, it1, it2, it3))
except ValueError:
    raise StopIteration

通过在每个迭代器上单独调用 next() 完全不使用 map()

a, b, c, d = next(it0), next(it1), next(it2), next(it3)

或使用列表理解:

a, b, c, d = [next(i) for i in (it0, it1, it2, it3)]

两者都确保 next() 在赋值发生之前被调用,而不是在赋值过程中被调用。

根据 Martijn Pieters 的回答,直接使用列表理解是可行的:

a, b, c, d = [next(it) for it in (it0, it1, it2, it3)]

这个 post 阐明了为什么 for 在可迭代对象上捕获 StopIteration 而不是在循环体中。

另一种可能的方式,利用nextdefault参数:

it0 = iter(['foo','bar'])
it1 = iter(['bar','foo'])
it2 = iter(['foo','foo'])
it3 = iter(['bar','bar'])

try:
    a, b, c, d = map(next, (it0, it1, it2, it3), (None,)*4)
    while all(_ is not None for _ in (a,b,c,d)):
        #some operations that can raise ValueError
        if a+b+c+d == 'foobar'*2:
            a, b, c, d = map(next, (it0, it1, it2, it3), (None,)*4)
        elif a+b == 'foobar':
            c, d = map(next,(it2, it3), (None,)*2)
        else:
            a, b = map(next,(it0, it1), (None,)*2)
except StopIteration:
    pass