如何使用列表推导式扁平化Python列表?
我最近在找一种方法,把一个嵌套的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 个回答
当然要注意,这种理解方式只会把一个列表中的列表(或者其他可迭代的东西)“压平”。如果你给它一个字符串列表,它会把它“压平”成一个字符列表。
为了更好地理解这个问题,首先你需要能够清楚地区分字符串(或者字节数组)和其他类型的序列(或者其他可迭代的对象)。所以我们先来写一个简单的函数:
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)都会被放在列表里返回。字典则会以键的列表形式返回(按照哈希顺序)。
这种方法确实可以用来把列表变平,但我不太推荐,除非你的子列表很小(每个只有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
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关于列表推导式的教程。
对于那些懒得查资料的开发者,这里有个快速的答案:
>>> a = [[1,2], [3,4]]
>>> [i for g in a for i in g]
[1, 2, 3, 4]
让我们先看看你的列表推导式,但首先我们从最简单的列表推导式开始。
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
。