高级嵌套列表推导式语法

47 投票
4 回答
61032 浏览
提问于 2025-04-16 04:26

我在玩列表推导式,想更好地理解它们,但遇到了一些意想不到的输出,我搞不懂为什么会这样。我没有找到之前有人问过这个问题,如果这是个重复的问题,我在这里先道个歉。

我其实是想写一个生成器,这个生成器可以生成其他生成器。一个简单的使用列表推导式的生成器看起来是这样的:

(x for x in range(10) if x%2==0) # generates all even integers in range(10)

我想做的是写一个生成器,它能生成两个生成器——第一个生成器生成0到9之间的偶数,第二个生成器生成0到9之间的奇数。为此,我做了:

>>> (x for x in range(10) if x%2==i for i in range(2))
<generator object <genexpr> at 0x7f6b90948f00>

>>> for i in g.next(): print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g = (x for x in range(10) if x%2==i for i in range(2))
>>> g
<generator object <genexpr> at 0x7f6b90969730>
>>> g.next()
Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 1, in <genexpr>
    UnboundLocalError: local variable 'i' referenced before assignment

但我不明白为什么会出现“在赋值之前引用了'i'”的错误。

我以为这可能和 i in range(2) 有关,所以我尝试了:

>>> g = (x for x in range(10) if x%2==i for i in [0.1])
>>> g
<generator object <genexpr> at 0x7f6b90948f00>
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment

但这对我来说没有意义,所以我觉得先试一些简单的东西比较好。于是我回到列表,尝试了:

>>> [x for x in range(10) if x%2==i for i in range(2)]
[1, 1, 3, 3, 5, 5, 7, 7, 9, 9]

我原本以为这和下面的结果是一样的:

>>> l = []
>>> for i in range(2):
...     for x in range(10):
...             if x%2==i:
...                     l.append(x)
... 
>>> l
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] # so where is my list comprehension malformed?

但当我凭直觉尝试时,这个却成功了:

>>> [[x for x in range(10) if x%2==i] for i in range(2)]
[[0, 2, 4, 6, 8], [1, 3, 5, 7, 9]] # so nested lists in nested list comprehension somehow affect the scope of if statements? :S

所以我想这可能是 if 语句的作用范围有问题。于是我又试了这个:

>>> [x for x in range(10) for i in range(2) if x%2==i]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

现在我彻底困惑了。有人能解释一下这种行为吗?我不明白为什么我的列表推导式看起来有问题,也不明白 if 语句的作用范围是怎么回事。

附注:在校对这个问题时,我意识到这看起来有点像作业问题——但其实不是。

4 个回答

10

Lie Ryan的for循环等价物让我想到以下这个,似乎运行得很好:

[x for i in range(2) for x in range(10) if i == x%2]

输出结果是

[0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
47

你需要使用一些括号:

((x for x in range(10) if x%2==i) for i in range(2))

这让我有点困惑,所以我觉得最好先尝试一些简单的东西。于是我回到列表,试了试:

[>>> [x for x in range(10) if x%2==i for i in range(2)] [1, 1, 3, 3, 5, 5, 7, 7, 9, 9]

这个可以正常工作,因为之前的列表推导式把变量 i 泄露到了外部作用域,变成了当前这个的 i。如果你尝试在一个新的 Python 解释器中运行这个,就会因为 NameError 出错。Python 3 中已经去掉了这种变量泄露的行为。

编辑:

下面这个等价的 for 循环:

(x for x in range(10) if x%2==i for i in range(2))

会是:

l = []
for x in range(10):
    if x%2 == i:
        for i in range(2):
            l.append(x)

这也会导致名称错误。

编辑2:

带括号的版本:

((x for x in range(10) if x%2==i) for i in range(2))

相当于:

li = []
for i in range(2):
    lx = []
    for x in range(10):
        if x%2==i:
            lx.append(x)
    li.append(lx)

撰写回答