解释器什么时候以及为什么会假定子列表的长度相同?

2024-04-20 14:03:42 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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期望的是什么,因为我希望预测格式错误的列表/子列表。你知道吗

我已经浏览了Python文档和Stackoverflow,但是还没有找到原因或者解释器是如何做到这一点的。你知道吗

我的猜测是,展平相同长度的数组是一种常见的现象(例如,机器学习降维、矩阵变换等),因此在无法完成上面尝试的工作的情况下,提供此功能是有用的。你知道吗


Tags: in元素most列表forstdinlinecall
3条回答

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

迭代for a,b,c in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]时,python返回一个iterator,它将迭代(返回)每个列表值。你知道吗

因此,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,因为只有一个值。你知道吗

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

每次迭代一个赋值,在循环体的开头进行—在您的例子中,它是一个解包赋值,绑定多个名称。你知道吗

为了与第二个例子完全等效,你的第一个例子是:

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

应该改为:

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

没有“预期”,也不可能,因为(在一般情况下)您可能在迭代任何内容,例如,从套接字传入的数据。你知道吗

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

>>> d = {}
>>> for k, d[k] in [[1, 2], [3, 4]]: 
...     pass 
...
>>> d
{1: 2, 3: 4}

这只是一堆作业,按照明确的顺序。你知道吗

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

Python实际上并没有做任何特殊的事情来实现这个功能;除了针对内置类型的特殊情况外,比如tuple(可能还有list),这个实现只是按照预期的次数迭代子iterable,并转储解释器堆栈上找到的所有值,然后将它们存储到提供的名称中。它还尝试再迭代一次(应为StopIteration),这样您就不会自动忽略额外的值。你知道吗

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

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,不要解包,只需存储到一个名称,并以任何对您的程序逻辑有意义的方式手动迭代该名称。你知道吗

相关问题 更多 >