题 在Python中列出列表中的平面列表


我想知道是否有一条快捷方式可以在Python列表中列出一个简单的列表。

我可以在for循环中做到这一点,但也许有一些很酷的“单行”?我尝试过 减少,但是我收到了一个错误。

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

错误信息

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

2083
2018-06-04 20:30


起源


这里有一个深入的讨论: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html,讨论了几种扁平化任意嵌套列表列表的方法。一个有趣的读物! - RichieHindle
其他一些答案更好,但你失败的原因是'extend'方法总是返回None。对于长度为2的列表,它将起作用但返回None。对于更长的列表,它将使用前2个args,它返回None。然后继续使用None.extend(<third arg>),这会导致此错误 - mehtunguh
@ shawn-chin解决方案在这里更加pythonic,但是如果你需要保留序列类型,假设你有一个元组元组而不是列表列表,那么你应该使用reduce(operator.concat,tuple_of_tuples)。使用带有元组的operator.concat似乎比带有list的chain.from_iterables执行得更快。 - Meitham
numpy.array([[1],[2]])。flatten()。tolist(),删除内部结构并返回列表[1,2] - user5920660
现在支持 mpu: import mpu; mpu.datastructures.flatten([1, [2, 3], [4, [5, 6]]]) 给 [1, 2, 3, 4, 5, 6] - Martin Thoma


答案:


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

意思是:

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

比目前发布的快捷方式快。 (l 是要变平的名单。)

这是一个相应的功能:

flatten = lambda l: [item for sublist in l for item in sublist]

对于证据,一如既往,你可以使用 timeit 标准库中的模块:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

说明:基于的快捷方式 + (包括隐含用途 sum),是必要的, O(L**2) 当有L个子列表时 - 随着中间结果列表越来越长,每个步骤都会分配一个新的中间结果列表对象,并且必须复制前一个中间结果中的所有项目(以及添加的一些新项目)最后)。所以(为了简单而没有实际的失去一般性)说你有每个项目的L个子列表:第一个I项目来回复制L-1次,第二个I项目L-2次,依此类推;总复制数是I乘以x的总和,从1到L排除,即 I * (L**2)/2

列表理解只生成一个列表一次,并将每个项目(从其原始居住地点到结果列表)复制一次。


3001
2018-06-04 20:37



我尝试使用相同的数据进行测试 itertools.chain.from_iterable : $ python -mtimeit -s'from itertools import chain; l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'list(chain.from_iterable(l))'。它的运行速度比嵌套列表理解的速度快两倍,这是此处显示的最快替代方案。 - intuited
我发现语法难以理解,直到我意识到你可以认为它与嵌套for循环完全一样。对于子列表中的子列表:对于子列表中的项:yield item - Rob Crowell
@BorisChervenkov:请注意我把电话打包了 list() 将迭代器实现到列表中。 - intuited
[树木中的树叶在树上的叶子]可能更容易理解和应用。 - John Mee
@Joel,实际上是现在 list(itertools.chain.from_iterable(l)) 是最好的 - 正如其他评论和肖恩的回答中所注意到的那样。 - Alex Martelli


您可以使用 itertools.chain()

>>> import itertools
>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))

或者,在Python> = 2.6上,使用 itertools.chain.from_iterable() 这不需要解压缩列表:

>>> import itertools
>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))

这种方法可以说更具可读性 [item for sublist in l for item in sublist] 并且看起来也更快:

[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
10000 loops, best of 3: 24.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 45.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 488 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 522 usec per loop
[me@home]$ python --version
Python 2.7.3

1083
2018-06-04 21:06



@ShawnChin BTW,你回答这个问题时的硬件,我现在的工作站快了一半,已经4年了。 - Manuel Gutierrez
@alexandre看 docs.python.org/2/tutorial/... - Shawn Chin
该 * 是一件棘手的事情 chain 不如列表理解那么直截了当。你必须知道链只将作为参数传递的迭代连接在一起,并且*导致顶级列表扩展为参数,所以 chain 将所有这些迭代连接在一起,但不会进一步下降。我认为这使得理解比在这种情况下使用链更具可读性。 - Tim Dierks
@TimDierks:我不确定“这需要你理解Python语法”是反对在Python中使用给定技术的论据。当然,复杂的使用可能会混淆,但“splat”操作符在许多情况下通常都很有用,而且这种操作并不是以特别模糊的方式使用它;拒绝所有对初学者来说不一定显而易见的语言特征意味着你将一只手绑在背后。不管你在做什么,也可以抛弃列表理解;来自其他背景的用户会找到一个 for反复循环 append更明显。 - ShadowRanger
关于什么 ['abcde_', ['_abcde', ['e_abcd', ['de_abc', ['cde_ab', ['bcde_a']]]]]] - Aymon Fournier


作者请注意:这是低效的。但很有趣,因为单子很棒。它不适合生产Python代码。

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

这只是对第一个参数中传递的iterable元素求和,将第二个参数视为总和的初始值(如果没有给出, 0 而是使用,这种情况会给你一个错误)。

因为你要对嵌套列表求和,所以你实际得到了 [1,3]+[2,4] 后果 sum([[1,3],[2,4]],[]),等于 [1,3,2,4]

请注意,仅适用于列表列表。对于列表列表,您需要另一种解决方案。


638
2018-06-04 20:35



这是非常整洁和聪明,但我不会使用它,因为它是令人困惑的阅读。 - andrewrk
这是画家的算法Shlemiel joelonsoftware.com/articles/fog0000000319.html  - 不必要的低效率以及不必要的丑陋。 - Mike Graham
列表上的追加操作形成了一个 Monoid,这是思考一个最方便的抽象之一 + 一般意义上的操作(不仅限于数字)。所以这个答案应该得到我的+1作为幺半群的(正确)处理列表。 虽然表现有关...... - ulidtko
@andrewrk嗯,有些人认为这是最干净的方法: youtube.com/watch?v=IOiZatlZtGU 那些不明白为什么这很酷的人只需要等待几十年,直到每个人都这样做:)让我们使用被发现但未被发明的编程语言(和抽象),Monoid被发现。 - jhegedus
由于总和的二次方面,这是一种非常低效的方式。 - Jean-François Fabre


我测试了大多数建议的解决方案 perfplot (我的一个宠物项目,基本上是一个包装 timeit),并找到了

list(itertools.chain.from_iterable(a))

成为最快的解决方案(如果连接的列表超过10个)。

enter image description here


重现情节的代码:

import functools
import itertools
import numpy
import operator
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def numpy_concatenate(a):
    return list(numpy.concatenate(a))


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    kernels=[
        forfor, sum_brackets, functools_reduce, itertools_chain, numpy_flat,
        numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )

131
2017-07-26 09:38





from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

extend() 示例中的方法修改 x 而不是返回一个有用的值(其中 reduce() 预计)。

一种更快的方法 reduce 版本将是

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

100
2018-06-04 20:35



reduce(operator.add, l) 这将是正确的方法 reduce 版。内置命令比lambdas快。 - agf
@agf这里是如何:* timeit.timeit('reduce(operator.add, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000)  0.017956018447875977   * timeit.timeit('reduce(lambda x, y: x+y, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000)  0.025218963623046875 - lukmdo
这是Shlemiel画家的算法joelonsoftware.com/articles/fog0000000319.html - Mike Graham
这只能用于 integers。但是如果列表包含什么呢 string? - Freddy
@Freddy:The operator.add 函数同样适用于整数列表和字符串列表。 - Greg Hewgill


以下是适用的一般方法 数字字符串嵌套 列表和  容器。

from collections import Iterable


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

注意:在Python 3中, yield from flatten(x) 可以取代 for sub_x in flatten(x): yield sub_x

演示

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst))                                         # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

参考

  • 此解决方案是从配方中修改的 Beazley,D。和B. Jones。食谱4.14,Python Cookbook 3rd Ed。,O'Reilly Media Inc. Sebastopol,CA:2013。
  • 发现较早 所以发帖,可能是最初的示范。

55
2017-11-29 04:14



我写的几乎一样,因为我没有看到你的解决方案......这就是我所寻找的“递归展平完整的多个列表”...(+ 1) - Martin Thoma
@MartinThoma非常感谢。仅供参考,如果扁平化嵌套迭代是一种常见的做法,那么有一些第三方软件包可以很好地处理这个问题。这可以避免重新发明轮子。我已经提到了 more_itertools 本文中讨论的其他内容。干杯。 - pylang
很好 - 只是想知道一个 yield from 学习后的python上的构造类型 yield * 在es2015。 - Triptych
替换为 if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)): 支持字符串。 - Jorge Leitão
正确。原始的食谱配方实际上展示了如何支持字符串和字节。如果已编辑它以反映此支持。 - pylang


我接受我的陈述。总和不是赢家。虽然列表很小但速度更快。但是,较大的列表会使性能显着下降。 

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

总和版本仍然运行超过一分钟,它还没有完成处理!

对于中型名单:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

使用小列表和timeit:number = 1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131

31
2018-06-04 20:46



对于一个真正微不足道的清单,例如一个有3个子列表,可能 - 但由于总和的性能与O(N ** 2)一致,而列表理解与O(N)一致,只是增加输入列表一点就会扭转事情 - 事实上LC将是“随着N的增长,“超过极限的总和”。我负责设计sum并在Python运行时进行第一次实现,我仍然希望我找到一种方法来有效地限制它来汇总数字(它真正擅长)并阻止它为人们提供的“有吸引力的滋扰”谁想要“总结”清单;-)。 - Alex Martelli


为什么使用extend?

reduce(lambda x, y: x+y, l)

这应该工作正常。


25
2018-06-04 20:38



这可能会创建许多很多中间列表。 - Reut Sharabani
对于python3 from functools import reduce - andorov
对不起,看到其余的答案真的很慢 - Mr_and_Mrs_D