Python 解释器在什么时候以及为什么通过假定相同长度的子列表来分解?

Python 解释器在什么时候以及为什么通过假定相同长度的子列表来分解?,python,flatten,Python,Flatten,一个简单的Pythonfor语句可以轻松地分解列表,而不需要numpy.undravel或等效的展平函数,这给我留下了深刻的印象,也让我很高兴。但是,现在的权衡是,我无法访问如下列表中的元素: 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> Va

一个简单的Python
for
语句可以轻松地分解列表,而不需要
numpy.undravel
或等效的展平函数,这给我留下了深刻的印象,也让我很高兴。但是,现在的权衡是,我无法访问如下列表中的元素:

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)

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

我想知道Python期望的是什么,因为我想预测格式错误的列表/子列表

我已经翻阅了Python文档和Stackoverflow,但还没有找到原因或解释器是如何做到这一点的

我的猜测是,展平相同长度的数组非常常见(例如,机器学习降维、矩阵变换等),在无法完成我上面尝试过的任务的情况下,提供此功能是有用的。

解释器在进行解包分配时总是假定长度匹配,如果长度不匹配,就会与
ValueError
崩溃。for循环实际上非常类似于一种“重复赋值语句”,其中LHS是循环的自由变量,而RHS是一个iterable容器,生成迭代的每个步骤中使用的连续值

每次迭代一个赋值,在循环体开始时进行——在您的例子中,它是一个解包赋值,绑定多个名称

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

for a,b,c in [[5],[6],[7]]:
    ...
应该改为写:

for a, in [[5],[6],[7]]:
    ...
不存在“预期”,也不可能存在,因为(在一般情况下)您可能正在迭代任何内容,例如从套接字传入的数据流

为了完全掌握for循环流是如何工作的,与赋值语句的类比非常有用。可以在赋值语句左侧使用的任何内容都可以用作for循环中的目标。例如,这相当于在dict中设置
d[1]=2
等,并应产生与
dict(RHS)
相同的结果:


这只是一系列的赋值,按照定义良好的顺序。

Python不知道,您只是告诉它通过解包为三个名称来期望三个元素。
ValueError
表示“您告诉了我们三个,但我们发现一个子iterable没有三个元素,我们不知道该怎么办”

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

对于有限的情况,您可以灵活地将其中一个解包名称前面加上一个
*
,以便将所有“不适合”元素捕获到该名称中(作为
列表
)。这样,您可以在允许更多元素的同时设置最小元素数,例如,如果您确实只需要第二个示例中的第一个元素,您可以执行以下操作:

for a, *_ in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]:
    print(a,b,c)
其中,
只是一个名称,按照惯例,它的意思是“我实际上并不关心这个值,但我需要一个占位符名称”

另一个例子是,您需要第一个和最后一个元素,但不关心中间的元素:

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

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

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

当您在[[1,2,3]、[4,5,6]、[7,8,9]、[0,0,0]、[5]]中迭代a、b、c时,发生的事情是python返回一个将迭代(返回)每个列表值的a

因此,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)
将返回列表中的每个元素,直到它根据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
,因为只有一个值。

对于[[5]、[6]、[7]]中的a、b、c:
来说,与numpy完全无关。这是一个Python列表。[1,2,3],[4,5,6],[7,8,9],[0,0,0],[5],
中的a,b,c也是如此:
首先,你不是在处理一个
numpy
行为。这是基本的Python迭代。其次,您似乎混淆了两个项目-迭代的
和解包的
a、b、c。对于预期的项目数,解包是不灵活的,在本例中为3(每个变量一个值)。此外,它也不允许你做任何假设——如果你弄错了,它会引发运行时
ValueError
。(这种不匹配不是语法错误)。它可以让你把任何东西都打开。由您来确保您的iterables具有预期的项目数。“那么,Python为什么允许我们假设列表总是具有相同数量的元素?”-同样的原因,它允许您在执行
l[2]
时假设列表至少有3个元素,或者为什么当你在l:s+=x中为x写
时,它让你假设列表中的每个元素都是一个数字。为什么它不让你呢?有很多方法可以用不相等的子序列来“分解”序列。看问题,不完全是;在[5]、[6]、[7]]中的[a]、[b]、[c]需要是:
(注意在迭代的东西上有额外的括号)。否则,它将试图将
[5]
解包到
[a]、[b]、[c]
。您的后期编辑方法也可以工作:-)。作为母鹿
l = [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]

l_iter = iter(l)

a,b,c = next(l_iter)
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]