【问题标题】:How does the list comprehension to flatten a python list work?扁平化 python 列表的列表理解如何工作?
【发布时间】:2014-10-29 17:11:10
【问题描述】:

我最近寻找一种将嵌套 python 列表展平的方法,如下所示:[[1,2,3],[4,5,6]],变为:[1,2,3,4,5 ,6]。

Stackoverflow 一如既往地很有帮助,我发现 a post 具有这种巧妙的列表理解:

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将它解释回前面,但显然情况并非总是如此。如果是,第一个理解应该抛出一个错误,因为“子列表”不存在。我的思想完全扭曲了,救命!

【问题讨论】:

  • 最好的办法是改成正常的for循环,用print看看发生了什么
  • 第二个推导只起作用,因为itemsublist 保留了第一个推导的最终值。首先运行那个(或del item; del sublist 然后执行),它会按预期给出NameError。这里的奇怪之处在于 Python 的作用域规则,而不是推导式。
  • @MarkWhitfield 仅在 python2 中...在 python3 中 list-comprehensions 表现为 genexps,并且不会泄漏外部范围内的变量。
  • 谢谢大家,现在完全有道理了!
  • 无论如何for 是按顺序排列的:您从左到右读取,这就是它们的执行顺序。最左边的for 循环,最右边的for 循环。当您将理解转换为显式循环时,您只需在 for 循环之间添加冒号即可:[item for sublist in l for item in sublist] "becomes" for sublist in l: for item in sublist: item

标签: python list-comprehension


【解决方案1】:

for 循环从左到右计算。任何list comprehension都可以重写为for循环,如下:

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

以上是扁平化列表的正确代码,无论您选择将其简洁地编写为列表推导式,还是在此扩展版本中。

您编写的第二个列表解析将引发 NameError,因为尚未定义“子列表”。您可以通过将列表推导式编写为 for 循环来看到这一点:

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

您在运行代码时没有看到错误的唯一原因是您之前在实现第一个列表解析时定义了子列表。

有关更多信息,您可能需要查看Guido's tutorial on list comprehensions

【讨论】:

    【解决方案2】:

    让我们看看你的列表解析,但首先让我们从最简单的列表解析开始。

    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 列表理解从最左边的 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

    【讨论】:

      【解决方案3】:

      当然,请注意,这种理解只会“扁平化”列表列表(或其他可迭代列表)。此外,如果您将字符串列表传递给它,您会将其“展平”为字符列表。

      要以有意义的方式概括这一点,您首先希望能够清楚地区分字符串(或字节数组)和其他类型的序列(或其他 Iterables)。所以让我们从一个简单的函数开始:

      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 的实例都以列表形式返回。字典作为键列表返回(按哈希顺序)。

      【讨论】:

        【解决方案4】:

        对于想要快速回答的懒惰开发者:

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

        【讨论】:

        • 这如何回答这个问题?
        【解决方案5】:

        虽然这种方法对于扁平化列表确实有效,但我不会推荐它,除非您的子列表已知非常小(每个 1 或 2 个元素)。

        我对@9​​87654321@ 进行了一些分析,发现这比使用单个循环并调用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
        

        【讨论】:

        • 为了进行分析,我使用了一个包含 1000 个子列表的列表,每个子列表包含 100,000 个整数。有了这些数据,使用extend 的执行时间大约提高了 3 倍。同样,如果您的所有子列表都只有一个元素,那么这两种方法的时间应该大致相等。
        • 顺便说一句,我真的很惊讶 Python 不包含分别使用这两种方法的 flatteniflatten 的实现。似乎是疏忽。
        猜你喜欢
        • 2014-08-26
        • 2017-02-27
        • 1970-01-01
        • 2016-01-14
        • 2010-12-13
        • 2012-07-01
        • 2013-04-06
        • 2017-09-24
        相关资源
        最近更新 更多