列表理解中的双重迭代


226

在Python中,列表推导中可以有多个迭代器,例如

[(x,y) for x in a for y in b]

对于一些合适的序列a和b。我知道Python的列表推导的嵌套循环语义。

我的问题是:理解中的一个迭代器可以引用另一个吗?换句话说:我可以有这样的东西:

[x for x in a for a in b]

外循环的当前值在哪里是内循环的迭代器?

例如,如果我有一个嵌套列表:

a=[[1,2],[3,4]]

列表理解表达式将如何获得此结果:

[1,2,3,4]

?? (请只列出理解答案,因为这是我要查找的内容)。

Answers:


178

要根据自己的建议回答您的问题:

>>> [x for b in a for x in b] # Works fine

当您要求提供列表理解答案时,我还要指出出色的itertools.chain():

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6

10
[x for b in a for x in b]这一直是关于python的错误。这种语法是如此倒退。常规形式的x for x in yalways总是在for之后直接包含变量,并馈给for左侧的表达式。一旦您进行了双重理解,您最近迭代的变量就会突然变得“远”。这是尴尬,不自然地在所有的读
排排坐

169

我希望这对别人a,b,x,y有帮助,因为对我没有太大的意义!假设您有一个充满句子的文本,并且想要一个单词数组。

# Without list comprehension
list_of_words = []
for sentence in text:
    for word in sentence:
       list_of_words.append(word)
return list_of_words

我喜欢将列表理解理解为水平扩展代码。

尝试将其分解为:

# List Comprehension 
[word for sentence in text for word in sentence]

例:

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> [word for sentence in text for word in sentence]
['Hi', 'Steve!', "What's", 'up?']

这也适用于发电机

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> gen = (word for sentence in text for word in sentence)
>>> for word in gen: print(word)
Hi
Steve!
What's
up?

8
“计算机科学中只有两个难题:缓存失效和命名。” -菲尔·卡尔顿(Phil Karlton)
塞萨尔(cezar)

这是一个很好的答案,因为它使整个问题变得不太抽象!谢谢!
A. Blesius

我想知道,您能否对列表理解中的三个抽象级别执行相同的操作?就像文字中的章节,章节中的句子以及句子中的单词一样?
福杰蒂船长

123

e,我想我找到了答案:我对那个循环是内部的,哪个循环是外部的,没有足够的小心。列表理解应为:

[x for b in a for x in b]

为了获得期望的结果,是的,一个当前值可以作为下一个循环的迭代器。


67
列表理解语法不是Python的亮点之一。
格伦·梅纳德

2
@Glenn是的,除了简单的表达式之外,它很容易让人费解。
ThomasH

1
w 我不确定这是列表理解的“常用”用法,但是非常不幸的是,Python中的链接是如此讨厌。
马特·乔纳

14
如果在每个“ for”之前放置换行符,则看起来非常干净。
Nick Garvey 2014年

16
哇,这完全颠倒了我的脑海。
obskyr

51

迭代器的顺序似乎违反直觉。

举个例子: [str(x) for i in range(3) for x in foo(i)]

让我们分解一下:

def foo(i):
    return i, i + 0.5

[str(x)
    for i in range(3)
        for x in foo(i)
]

# is same as
for i in range(3):
    for x in foo(i):
        yield str(x)

4
真是大开眼界!
nehem

我的理解是,这样做的原因是“列出的第一个迭代是如果将理解编写为嵌套for循环将被键入的最顶层的迭代”。这是违反直觉的原因是,OUTER循环(如果写为嵌套的for循环,则为最高)出现在方括号列表/ dict(理解对象)的INSIDE处。相反,INNER循环(当写​​为嵌套的for循环时,最里面)恰恰是理解中最右边的循环,并且以这种方式出现在理解的外面。
Zach Siegel

抽象地写我们[(output in loop 2) (loop 1) (loop 2)](loop 1) = for i in range(3)(loop 2) = for x in foo(i):(output in loop 2) = str(x)
Qaswed

20

ThomasH已经添加了一个很好的答案,但是我想说明会发生什么:

>>> a = [[1, 2], [3, 4]]
>>> [x for x in b for b in a]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

>>> [x for b in a for x in b]
[1, 2, 3, 4]
>>> [x for x in b for b in a]
[3, 3, 4, 4]

我猜想Python从左到右解析列表理解。这意味着,将首先for执行发生的第一个循环。

第二个“问题”是b从列表理解中“泄漏”出去。在第一次成功理解清单之后b == [3, 4]


3
有趣的一点。我对此感到惊讶:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
格林奇(Grinch)2014年

2
:该泄漏是固定在Python 3 stackoverflow.com/questions/4198906/...
德尼尔森Sá马亚

10

如果要保留多维数组,则应嵌套数组括号。请参阅下面的示例,其中每个元素都添加了一个。

>>> a = [[1, 2], [3, 4]]

>>> [[col +1 for col in row] for row in a]
[[2, 3], [4, 5]]

>>> [col +1 for row in a for col in row]
[2, 3, 4, 5]

8

这种记忆技术对我有很大帮助:

[ <RETURNED_VALUE> <OUTER_LOOP1> <INNER_LOOP2> <INNER_LOOP3> ... <OPTIONAL_IF> ]

现在,你可以想想[R E打开+ Ø uter环作为唯一的[R飞行Ø刻申

综上所述,即使对于3个循环,列表中的顺序也很容易:


c=[111, 222, 333]
b=[11, 22, 33]
a=[1, 2, 3]

print(
  [
    (i, j, k)                            # <RETURNED_VALUE> 
    for i in a for j in b for k in c     # in order: loop1, loop2, loop3
    if i < 2 and j < 20 and k < 200      # <OPTIONAL_IF>
  ]
)
[(1, 11, 111)]

因为上面只是一个:

for i in a:                         # outer loop1 GOES SECOND
  for j in b:                       # inner loop2 GOES THIRD
    for k in c:                     # inner loop3 GOES FOURTH
      if i < 2 and j < 20 and k < 200:
        print((i, j, k))            # returned value GOES FIRST

对于迭代一个嵌套列表/结构,技术是相同的:a从问题出发:

a = [[1,2],[3,4]]
[i2    for i1 in a      for i2 in i1]
which return [1, 2, 3, 4]

互相嵌套的水平

a = [[[1, 2], [3, 4]], [[5, 6], [7, 8, 9]], [[10]]]
[i3    for i1 in a      for i2 in i1     for i3 in i2]
which return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

等等


谢谢,但是您所描述的实际上是最简单的情况,其中涉及的迭代器是独立的。实际上,在您的示例中,您可以按任何顺序使用迭代器并且将获得相同的结果列表(模排序)。我更感兴趣的情况是使用嵌套列表,其中一个迭代器变为下一个迭代器。
ThomasH

@ThomasH:以粗体定义的循环顺序完全符合您的需求。在底部添加了一个示例来覆盖您的数据,并在另一个示例中添加了额外的嵌套级别。
斯瓦沃米尔Lenart

5

我觉得这更容易理解

[row[i] for row in a for i in range(len(a))]

result: [1, 2, 3, 4]

3

另外,您可以对当前访问的输入列表的成员该成员中的元素使用相同的变量。但是,这甚至可能使它(清单)更加难以理解。

input = [[1, 2], [3, 4]]
[x for x in input for x in x]

首先for x in input求值,得到输入的一个成员列表,然后,Python遍历第二部分,for x in x在此过程中,x值被其访问的当前元素覆盖,然后第一部分x定义了我们要返回的内容。


1

这个flatten_nlevel函数递归调用嵌套的list1以隐蔽到一个级别。试试看

def flatten_nlevel(list1, flat_list):
    for sublist in list1:
        if isinstance(sublist, type(list)):        
            flatten_nlevel(sublist, flat_list)
        else:
            flat_list.append(sublist)

list1 = [1,[1,[2,3,[4,6]],4],5]

items = []
flatten_nlevel(list1,items)
print(items)

输出:

[1, 1, 2, 3, 4, 6, 4, 5]

1
好的,问题特别是关于列表理解的,列表扁平化只是一个例子。但是我认为,您的广义列表展平器将需要递归调用自身。所以可能更像是flatten_nlevel(sublist, flat_list)吧?!
ThomasH
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.