解释器何时以及为何通过假设相同长度的子列表来解开?

When and why does the interpreter unravel by assuming same length sublists?

令我印象深刻的是,一个简单的 Python for 语句可以轻松解开列表的列表,而不需要 numpy.unravel 或等效的展平函数.但是,现在的权衡是我无法像这样访问列表的元素:

for a,b,c in [[5],[6],[7]]:
     print(str(a),str(b),str(c))
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 1)

相反,这有效,直到长度为 1 [5]

for a,b,c in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]:
     print(a,b,c)

1 2 3
4 5 6
7 8 9
0 0 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 1)

从逻辑上讲,假设列表具有固定数量的元素是没有意义的。那么,为什么 Python 允许我们假设列表的列表总是具有相同数量的元素?

我想知道 Python 期望的内容,因为我想预测格式错误的 lists/sublists。

我查阅了 Python 文档和 Whosebug,但没有找到原因或解释器是如何做到这一点的。

我的猜测是展平相同长度的数组是很常见的事情(例如机器学习降维、矩阵变换等),以提供此功能作为权衡无法做到这一点是有用的我在上面尝试过的。

解释器总是假设在进行解包赋值时长度是匹配的,如果不匹配就会崩溃ValueError。 for 循环实际上非常类似于一种 "repeated assignment statement",其中 LHS 是循环的自由变量,RHS 是一个可迭代的容器,产生连续的值以在每个步骤中使用的迭代。

每次迭代一个赋值,在循环体的开头进行 - 在您的情况下,它是一个解包赋值,它绑定多个名称。

因此,为了与第二个示例正确等效,您的第一个示例是:

for a,b,c in [[5],[6],[7]]:
    ...

应该改为:

for a, in [[5],[6],[7]]:
    ...

没有 "anticipation",也不可能有,因为(在一般情况下)您可能会迭代任何东西,例如从套接字流入的数据。

为了完全掌握 for 循环流程的工作原理,与赋值语句的类比非常有用。任何可以在赋值语句左侧使用的东西,都可以用作 for 循环中的目标。例如,这相当于在字典中设置 d[1] = 2 等 - 并且应该与 dict(RHS):

产生相同的结果
>>> d = {}
>>> for k, d[k] in [[1, 2], [3, 4]]: 
...     pass 
...
>>> d
{1: 2, 3: 4}

这只是一堆作业,顺序明确。

Python 不知道,您只是 告诉 它通过解包为三个名称来期望三个元素。 ValueError 表示 "you told us three, but we found a sub-iterable that didn't have three elements, and we don't know what to do"。

Python 并没有做任何特别的事情来实现它;除了像 tuple(可能还有 list)这样的内置类型的特殊情况,实现只是迭代子可迭代的预期次数并转储在解释器堆栈中找到的所有值,然后将它们存储到提供的名称中。它还会尝试再迭代一次(预计 StopIteration),这样您就不会默默地忽略额外的值。

对于有限的情况,您可以灵活地在其中一个解包名称前面加上 *,这样您就可以将所有 "didn't fit" 元素捕获到该名称中(作为 list).这使您可以设置最少数量的元素,同时允许更多元素,例如如果你真的只需要第二个例子中的第一个元素,你可以这样做:

for a, *_ in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]:
    print(a,b,c)

其中 _ 只是一个名称,按照惯例,意思是 "I don't actually care about this value, but I needed a placeholder name"。

另一个例子是当你想要第一个和最后一个元素,而不关心中间的元素时:

for first, *middle, last in myiterable:
    ...

但除此之外,如果您需要处理可变长度的可迭代对象,请不要解包,只需存储到一个名称并以对您的程序逻辑有意义的任何方式手动迭代该名称。

Python 不假定相同长度的列表,因为这不仅适用于列表。

当您迭代 for a,b,c in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]] 时,发生的事情是 python return 一个 iterator 将迭代(return)每个列表值。

因此 for 等同于:

l = [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]

l_iter = iter(l)

a,b,c = next(l_iter)

next(l_iter) 将 return 列表中的每个元素,直到它根据 python 迭代协议引发 StopIteration 执行。

这意味着:

a,b,c = [1,2,3]
a,b,c = [4,5,6]
a,b,c = [7,8,9]
a,b,c = [0,0,0]
a,b,c = [5]

正如您现在所见,python 无法将 [5] 解压为 a,b,c,因为只有一个值。