Python列表推导式双重循环

90
vec = [[1,2,3], [4,5,6], [7,8,9]]
print [num for elem in vec for num in elem]      <----- this

>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]

这让我感到困惑。
我理解for elem in vic中的elem是列表中的列表。
但我不太明白一开始和结尾处的numfor num in elem的用法。

Python如何解释这个代码?
它的执行顺序是怎样的?


可能是列表推导中的双重迭代的重复问题。 - Inbar Rose
3
@Inbar,那个不是很好的副本目标,因为它未能解释Python如何解释嵌套循环的顺序。 - Martijn Pieters
相关/可能重复:嵌套列表理解 - Martijn Pieters
4个回答

166

让我们来分解一下。

一个简单的列表推导式:

[x for x in collection]

如果我们将其分解成几个部分,就容易理解了:[A for B in C]

  • A是结果列表中的项
  • B是集合C中的每一项
  • C是集合本身。

通过这种方式,可以编写如下代码:

[x.lower() for x in words]
为了将列表中的所有单词转换为小写。
当我们将其与另一个列表合并时,情况会变得更加复杂,如下所示:
[x for y in collection for x in y] # [A for B in C for D in E]

在这里,有些特别的事情发生了。我们希望最终列表包括A项,而A项在B项中找到,因此我们需要告诉列表推导式。

  • A是将出现在结果列表中的项
  • B是集合C中的每个项
  • C是集合本身
  • D是集合E中的每个项(在这种情况下也是A
  • E是另一个集合(在这种情况下为B

这个逻辑类似于常规的for循环:

for y in collection:     #      for B in C:
    for x in y:          #          for D in E: (in this case: for A in B)
        # receive x      #              # receive A

为了更好地解释并给出一个很好的例子+解释,想象一下有一辆火车。

火车头(前面)永远会存在(列表推导式的结果)

然后,有任意数量的火车车厢,每个火车车厢都采用以下形式:for x in y

列表推导式可能是这样的:

[z for b in a for c in b for d in c ... for z in y]

这就像是有一个常规的 for 循环:

for b in a:
    for c in b:
        for d in c:
            ...
                for z in y:
                    # have z

换句话说,在列表推导式中,不需要换行和缩进,只需将下一个循环添加到末尾。

回到列车类比:

Engine - Car - Car - Car ... Tail

什么是“Tail”?在列表推导式中,“Tail”是一种特殊的东西。你不需要一个,“Tail”是一个条件。看这个例子:

[line for line in file if not line.startswith('#')] 

只要一行不以井号(#)开头,这将会返回文件中的每一行,其他的行则被跳过。

使用“尾部”的技巧是,在所有循环结束后,同时检查其为True/False,并获得最终的“引擎”或“结果”。在常规for循环中,上述示例如下:

for line in file:
    if not line.startswith('#'):
        # have line

请注意:虽然我的火车类比中只有在火车尾部存在一个“尾巴”,但这个条件或“尾巴”可以出现在每节“车厢”或循环之后...

例如:

>>> z = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> [x for y in z if sum(y)>10 for x in y if x < 10]
[5, 6, 7, 8, 9]
在常规的for循环中:
>>> for y in z:
    if sum(y)>10:
        for x in y:
            if x < 10:
                print x

5
6
7
8
9

你的回答暗示了你只能在列表推导式的末尾使用 if 语句。这是不正确的。你可以在列表推导式的任何级别上使用 if 语句,甚至可以将多个 if 语句放在一起(尽管在你意识到它们是嵌套的之前,这看起来有点荒谬)。你只是不能将 if 用作第一个项目,必须是一个 for 循环。 - Martijn Pieters
3
你是正确的,我并不是想暗示那个意思,我会将其表达得更加清楚明白。 - Inbar Rose
我必须说,你的解释过于复杂了。特别是当你开始将 A for B in C for D in E 扩展成一个嵌套循环,包括 for B in C:for A in B,丢弃 for D in E 部分时。 - Martijn Pieters
@Martin:你不能在以下形式的for循环之前使用if语句吗?A = [[[y for y in xrange(3)],2][x<=3] for x in xrange(7)] - Stefan Gruenwald

10

列表推导式文档中可知:

当提供列表推导式时,它由单个表达式组成,后跟至少一个 for 子句和零个或多个 if 子句。在这种情况下,新列表的元素是通过将每个 forif 子句视为块,从左到右嵌套,并评估表达式以在达到最内层块时生成列表元素来产生的。

换句话说,假设 for 循环是嵌套的。从左到右读取您的列表推导式可以被嵌套为:

for elem in vec:
    for num in elem:
        num           # the *single expression* from the spec

列表推导式将使用最后一个、最内层的块作为生成结果列表的值。


7

你可以将列表推导式看作是连续语句。这适用于任何级别的 forif 语句。

例如,考虑带有各自 if 的双重 for 循环:

vec = [[1,2,3], [4,5,6], [7,8,9]]
result = [i for e in vec if len(e)==3 for i in e if i%2==0]

这里的列表推导式与以下代码是相同的:

result = []
for e in vec: 
    if len(e)==3:
        for i in e:
            if i%2==0:
                result.append(i)

正如你所看到的,列表推导式只是使用forif而已,不需要缩进但顺序要相同。

2
这个答案真是太棒了。它提供了正确的视角,使其简洁明了,甚至比每个人预期的都更广泛适用。此外,它甚至更容易记忆和理解,所以非常感谢! - 16807

6

您的代码相当于:

temp = []
for elem in vec:
    for num in elem:
        temp.append(num)

6
请注意,在列表推导式中,for语句的书写顺序与您只编写常规for循环时所编写的顺序相同。 - kindall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接