我发现这种理解能够很好地平滑列表列表:
>>> 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]
我觉得我很难理解,因为我不太明白发电机是如何工作的......我的意思是,我以为我做了,但现在我真的很怀疑。就像我说的那样,我喜欢这个成语是多么紧凑,这正是我所需要的,但我不愿意使用我不理解的代码。
任何人都可以解释这里到底发生了什么吗?
答案 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}}首先出现,而不是最后一个循环的主体。