【问题标题】:understanding list comprehension for flattening list of lists in python理解列表理解以扁平化python中的列表列表
【发布时间】:2014-08-26 12:41:09
【问题描述】:

我发现这种理解非常适合扁平化列表:

>>> list_of_lists = [(1,2,3),(2,3,4),(3,4,5)]
>>> [item for sublist in list_of_lists for item in sublist]
[1, 2, 3, 2, 3, 4, 3, 4, 5]

我比使用itertools.chain() 更喜欢这个,但我就是无法理解。我试过用括号括起来的部分,看看我是否可以降低复杂性,但现在我更困惑了:

>>> [(item for sublist in list_of_lists) for item in sublist]
[<generator object <genexpr> at 0x7ff919fdfd20>, <generator object <genexpr> at 0x7ff919fdfd70>, <generator object <genexpr> at 0x7ff919fdfdc0>]

>>> [item for sublist in (list_of_lists for item in sublist)]
[5, 5, 5]

我觉得我很难理解,因为我不太了解发电机的工作原理......我的意思是,我以为我做到了,但现在我很怀疑。就像我说的,我喜欢这个成语的简洁性,这正是我所需要的,但我讨厌使用我不理解的代码。

谁能解释这里到底发生了什么?

【问题讨论】:

  • 您好!今天有没有什么改变让你选择了一个不同的接受答案?只是想了解;接受标记完全是您的选择!这么长时间后看到它改变有点罕见:-)
  • 呃,老实说我不确定?我想我是从一些收到 SO 徽章的人那里被引导到这里的,并且可能不小心点击了不同的接受标记。
  • 酷,感谢您的回复!我总是尽量让我的答案尽可能有帮助,如果我达到目标,接受标记是一个很好的指标。 :-)

标签: python list generator list-comprehension


【解决方案1】:

列表理解

当我第一次开始使用列表理解时,我像阅读英语句子一样阅读它,并且能够轻松理解它们。例如,

[item for sublist in list_of_lists for item in sublist]

读起来像

for each sublist in list_of_lists and for each item in sublist add item

另外,过滤部分可以读作

for each sublist in list_of_lists and for each item in sublist add item only if it is valid

相应的理解是

[item for sublist in list_of_lists for item in sublist if valid(item)]

发电机

它们就像地雷,只有在使用next 协议调用时才会触发。它们类似于函数,但在引发异常或到达函数末尾之前,它们不会被耗尽,并且可以一次又一次地调用。重要的是,它们保留了先前调用和当前调用之间的状态。

生成器和函数的区别在于,生成器使用yield 关键字将值返回给调用者。在生成器表达式的情况下,它们类似于列表推导式,第一个表达式是“产生”的实际值。

有了这个基本的了解,如果我们看看你在问题中的表达,

[(item for sublist in list_of_lists) for item in sublist]

您将列表理解与生成器表达式混合在一起。会这样读

for each item in sublist add a generator expression which is defined as, for every sublist in list_of_lists yield item

这不是你的想法。并且由于生成器表达式未迭代,因此生成器表达式对象按原样添加到列表中。因为它们不会在没有被下一个协议调用的情况下被评估,所以它们不会产生任何错误(如果有的话,除非它们有语法错误)。在这种情况下,它将产生运行时错误,因为sublist 尚未定义。

另外,在最后一种情况下,

[item for sublist in (list_of_lists for item in sublist)]
for each sublist in the generator expression, add item and the generator expression is defined as for each item in sublist yield list_of_lists.

for 循环将使用下一个协议迭代任何可迭代对象。因此,生成器表达式将被评估,item 将始终是sublist 迭代中的最后一个元素,并且您将其添加到列表中。这也会产生运行时错误,因为尚未定义子列表。

【讨论】:

  • 我可以在列表理解中为每次迭代返回带有产量的值
【解决方案2】:

像嵌套一样从左到右阅读 for 循环。左边的表达式是产生最终列表中每个值的表达式:

for sublist in list_of_lists:
    for item in sublist:
        item  # added to the list

列表推导还支持if 测试来过滤使用的元素;这些也可以看作是嵌套语句,就像for 循环一样。

通过添加括号,您更改了表达式;括号中的所有内容现在都是要添加的左侧表达式:

for item in sublist:
    (item for sublist in list_of_lists)  # added to the list

像这样的for 循环是一个生成器表达式。它的工作原理与列表推导完全一样,只是它不构建列表。相反,这些元素是按需生产的。您可以向生成器表达式询问下一个值,然后是下一个值,等等。

在这种情况下,必须有一个预先存在的sublist 对象才能使其正常工作;毕竟,外部循环还没有结束list_of_lists

您的最后一次尝试转化为:

for sublist in (list_of_lists for item in sublist):
    item  # aded to the list

这里的list_of_lists 是在for item in sublist 上循环的生成器表达式中的循环元素。同样,sublist 必须已经存在才能使其工作。然后循环将预先存在的item 添加到最终列表输出中。

在您的情况下,sublist 显然是一个包含 3 个项目的列表;你的最终清单产生了 3 个元素。 item 绑定到 5,所以你的输出中有 3 次 5

【讨论】:

    【解决方案3】:

    列表推导的工作方式如下:

    [<what i want> <for loops in the order you'd write them naturally>]
    

    在这种情况下,&lt;what I want&gt; 是每个 sublist 中的每个 item。要获取这些项目,您只需遍历原始列表中的子列表,然后保存/生成子列表中的每个项目。因此,列表推导中 for 循环的顺序与不使用列表推导时使用的顺序相同。唯一令人困惑的部分是 &lt;what I want&gt; 首先出现,而不是在最后一个循环的主体内。

    【讨论】:

    • 可能是对嵌套推导最人性化的解释。谢谢@timgeb
    猜你喜欢
    • 2014-10-29
    • 1970-01-01
    • 1970-01-01
    • 2019-11-11
    • 2018-09-12
    • 2016-01-14
    • 2023-04-08
    • 2017-02-27
    • 1970-01-01
    相关资源
    最近更新 更多