理解列表理解,以便在python中展平列表列表

时间:2014-07-05 13:08:04

标签: python list generator list-comprehension

我发现这种理解能够很好地平滑列表列表:

>>> 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]

我觉得我很难理解,因为我不太明白发电机是如何工作的......我的意思是,我以为我做了,但现在我真的很怀疑。就像我说的那样,我喜欢这个成语是多么紧凑,这正是我所需要的,但我不愿意使用我不理解的代码。

任何人都可以解释这里到底发生了什么吗?

3 个答案:

答案 0 :(得分:11)

列表理解

当我第一次开始列表理解时,我读到的就像英语句子一样,我能够很容易地理解它们。例如,

[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迭代中的最后一个元素,并且您将在列表中添加该元素。这也会产生运行时错误,因为尚未定义子列表。

答案 1 :(得分:9)

从左到右阅读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,因此您的输出中有5次。{/ p>

答案 2 :(得分:1)

列表理解的工作原理如下:

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

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