【问题标题】:ValueError raised before StopIteration with map(next, iterables)在使用 map(next, iterables) 的 StopIteration 之前引发 ValueError
【发布时间】:2016-08-21 22:40:43
【问题描述】:

我正在编写这样的 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 中运行这段代码时,我得到了一个 ValueError: not enough values to unpack (expected 2, got 0),在预期的 StopIteration 之前提出。

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

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

在 Python2.7 中,首先引发 StopIteration

【问题讨论】:

  • 您可能应该修复错误,但仅供参考,您可以捕获不止一种类型的异常而不捕获所有内容:except StopIteration, ValueError: do_something()
  • 酷酷.. 你的问题让我相信你不知道这一点。它写得好像你唯一的选择是捕捉要么StopIterationValueError;不是都。无论如何,我认为 Pieters 先生可能已经解决了下面的实际问题。
  • @jDo,我只想捕获 StopIteration 以停止 while 循环,但如果出现 ValueError,我希望程序停止。如果两者都被捕获并且 ValueError 可能意味着 StopIteration,那就令人困惑了。正如你所说,问题解决了。
  • @JacquesGaudin 看来您正试图在问题本身中为您的问题提供解决方案。这就是 answers 的用途。用对你有用的方法来回答你自己的问题是完全可以的,甚至受到鼓励。
  • 好的,我不想对完成大部分工作的 Martijn Pieters 无礼......将创建另一个答案。

标签: python python-3.x iterator map-function


【解决方案1】:

StopIteration 被提升,但元组赋值将其吞下,因为它将 StopIteration 视为来自 map() 迭代器的信号,表明 已完成生成值:

>>> 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 作为“迭代完成”信号,并且元组解包必须在此处迭代。

要么先将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 异常。

捕获ValueError 只是为了map() 操作:

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(),而不是在分配本身期间。

【讨论】:

  • 谢谢,现在更有意义了。请问为什么Python2.7的行为不一样?
  • @JacquesGaudin: map() 在 Python 2.7 中返回一个列表。这意味着它必须首先生成该列表才能进行元组分配,因此StopIteration 异常会在元组分配迭代之外引发。
  • 这更有意义!非常感谢您的及时答复。
【解决方案2】:

根据 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

【讨论】:

  • 我也将这种方法纳入了我的回答中;它与首先在单个迭代器上使用 next() 基本相同,关键是两者都避免使用生成器来推迟使用 next() 直到迭代分配。
  • 当然,值得注意的是 for 循环体中的 StopIteration 并没有被吞掉。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-14
  • 2013-01-03
  • 2022-01-26
  • 1970-01-01
  • 2021-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多