如何使用列表推导式扁平化Python列表?

36 投票
5 回答
34297 浏览
提问于 2025-04-19 21:35

我最近在找一种方法,把一个嵌套的Python列表,比如说[[1,2,3],[4,5,6]],变成一个平坦的列表,也就是[1,2,3,4,5,6]。

在StackOverflow上,我找到了一个很有用的帖子,里面有一个聪明的列表推导式:

l = [[1,2,3],[4,5,6]]
flattened_l = [item for sublist in l for item in sublist]

我以为我明白列表推导式是怎么回事,但显然我还是搞不清楚。让我最困惑的是,除了上面的推导式,这个也能运行(虽然结果不一样):

exactly_the_same_as_l = [item for item in sublist for sublist in l]

有人能解释一下Python是怎么理解这些东西的吗?根据第二个推导式,我本以为Python是从后往前解析的,但显然并不总是这样。如果真是这样,第一个推导式应该会报错,因为'sublist'并不存在。我的脑袋完全转不过来了,求助!

5 个回答

1

当然要注意,这种理解方式只会把一个列表中的列表(或者其他可迭代的东西)“压平”。如果你给它一个字符串列表,它会把它“压平”成一个字符列表。

为了更好地理解这个问题,首先你需要能够清楚地区分字符串(或者字节数组)和其他类型的序列(或者其他可迭代的对象)。所以我们先来写一个简单的函数:

import collections
def non_str_seq(p):
    '''p is putatively a sequence and not a string nor bytearray'''
    return isinstance(p, collections.Iterable) and not (isinstance(p, str) or isinstance(p, bytearray))

有了这个,我们就可以构建一个递归函数来压平任何

def flatten(s):
    '''Recursively flatten any sequence of objects
    '''
    results = list()
    if non_str_seq(s):
        for each in s:
            results.extend(flatten(each))
    else:
        results.append(s)
    return results

可能还有更优雅的方法来实现这个功能。但我知道的所有Python内置类型,这个方法都能用。简单的对象(比如数字、字符串、None、True、False)都会被放在列表里返回。字典则会以键的列表形式返回(按照哈希顺序)。

3

这种方法确实可以用来把列表变平,但我不太推荐,除非你的子列表很小(每个只有1或2个元素)。

我用 timeit 做了一些测试,发现这种方法大约比用单个循环和调用 extend 要慢2到3倍。

def flatten(l):
    flattened = []
    for sublist in l:
        flattened.extend(sublist)
    return flattened

虽然这种方法看起来不那么优雅,但速度提升是很明显的。我想这之所以有效,是因为 extend 可以一次性更高效地复制整个子列表,而不是一个一个地复制每个元素。如果你的子列表中等大小或更大,我建议使用 extend。子列表越大,速度提升越明显。

最后一点注意:显然,这种方法只有在你需要急切地生成这个平坦的列表时才有效。比如你可能之后还要对它进行排序。如果你最终只是想逐个遍历这个列表,那么这种方法并不会比其他人提到的嵌套循环方法更好。不过在这种情况下,你应该返回一个生成器,而不是列表,这样可以享受懒加载的好处……

def flatten(l):
    return (item for sublist in l for item in sublist) # note the parens
4

for循环是从左到右进行评估的。任何一个 列表推导式 都可以改写成for循环,像这样:

l = [[1,2,3],[4,5,6]]
flattened_l = []
for sublist in l:
    for item in sublist:
        flattened_l.append(item)

上面的代码是正确的,可以用来将一个列表扁平化,无论你选择用简洁的列表推导式写,还是用这个扩展的版本。

你写的第二个列表推导式会引发一个NameError,因为'sublist'还没有被定义。你可以通过把这个列表推导式写成for循环来看到这一点:

l = [[1,2,3],[4,5,6]]
flattened_l = []
for item in sublist:
    for sublist in l:
        flattened_l.append(item)

你在运行代码时没有看到错误的唯一原因是,你在实现第一个列表推导式时已经定义了sublist。

想了解更多信息,你可以查看 Guido关于列表推导式的教程

5

对于那些懒得查资料的开发者,这里有个快速的答案:

>>> a = [[1,2], [3,4]]
>>> [i for g in a for i in g]
[1, 2, 3, 4]
53

让我们先看看你的列表推导式,但首先我们从最简单的列表推导式开始。

l = [1,2,3,4,5]
print [x for x in l] # prints [1, 2, 3, 4, 5]

你可以把这个看作是一个这样的for循环:

for x in l:
    print x

现在我们来看另一个例子:

l = [1,2,3,4,5]
a = [x for x in l if x % 2 == 0]
print a # prints [2,4]

这个和下面这个是完全一样的:

a = []
l = [1,2,3,4,5]
for x in l:
    if x % 2 == 0:
        a.append(x)
print a # prints [2,4]

现在我们来看看你提供的例子。

l = [[1,2,3],[4,5,6]]
flattened_l = [item for sublist in l for item in sublist]
print flattened_l # prints [1,2,3,4,5,6]

对于列表推导式,从最左边的for循环开始,逐步向内看。这里的变量item就是要被添加的内容。它会产生这个等价的结果:

l = [[1,2,3],[4,5,6]]
flattened_l = []
for sublist in l:
    for item in sublist:
        flattened_l.append(item)

现在来看最后一个例子。

exactly_the_same_as_l = [item for item in sublist for sublist in l]

利用相同的知识,我们可以创建一个for循环,看看它的表现:

for item in sublist:
    for sublist in l:
        exactly_the_same_as_l.append(item)

上面的代码之所以能工作,是因为在创建flattened_l的时候,也同时创建了sublist。这是一个作用域的问题,为什么没有抛出错误。如果你在没有先定义flattened_l的情况下运行这段代码,你会得到一个NameError

撰写回答