【问题标题】:Advanced Nested List Comprehension Syntax高级嵌套列表理解语法
【发布时间】:2011-04-15 13:37:14
【问题描述】:

我正在玩弄列表推导以更好地理解它们,但我遇到了一些我无法解释的意外输出。我之前没有发现这个问题被问过,但如果它/是/重复的问题,我很抱歉。

我本质上是在尝试编写一个生成器的生成器。一个使用列表推导的简单生成器如下所示:

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

我试图做的是编写一个生成器,它生成两个生成器——第一个生成范围 (10) 中的偶数,第二个生成范围 (10) 中的奇数。为此,我做了:

>>> (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 语句的作用域是如何工作的。

PS:在校对问题时,我意识到这看起来确实有点像家庭作业问题 - 它不是。

【问题讨论】:

标签: python syntax scope list-comprehension


【解决方案1】:

你需要使用一些括号:

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

这对我来说没有意义,所以我 认为最好尝试一下 先简单点。所以我回到列表 并尝试过:

[>>> [x 表示 x 在 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)

这也会导致名称错误。

EDIT2:

括号内的版本:

((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)

【讨论】:

  • 谢谢,泄漏错误我明白了,但是为什么需要括号呢?没有括号的代码会转换成什么(就 for 循环而言)?我知道括号可以解决问题,但我不明白为什么
  • 不幸的是,取决于您使用结果的方式,第一个生成器可以给出偶数、奇数,或者一些偶数后跟一些奇数。
  • @asmoore82:它没有改变,它总是返回一个生成器。它只是在“等效”版本中,我抢先展开生成器以使事情更容易理解。
  • 哦,哇,感谢您的回复,我删除了我的评论,因为我误读了 OP 专门要求生成器生成器
【解决方案2】:

Lie 有句法问题的答案。一个建议:不要在发电机的主体里塞太多东西。函数更具可读性。

def make_generator(modulus):
    return (x for x in range(10) if x % 2 == modulus)
g = (make_generator(i) for i in range(2))

【讨论】:

  • 谢谢。我理解这一点,我更喜欢这个。但我正在尝试对列表理解进行一些练习,看看我能把它们推到多远
【解决方案3】:

稍微扩展一下 Lie Ryan 的回答:

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

相当于:

def _gen1():
    for x in range(10):
        if x%2 == i:
            for i in range(2):
                yield x
something = _gen1()

而带括号的版本相当于:

def _gen1():
    def _gen2():
        for x in range(10):
            if x%2 == i:
                yield x

    for i in range(2):
        yield _gen2()
something = _gen1()

这确实产生了两个生成器:

[<generator object <genexpr> at 0x02A0A968>, <generator object <genexpr> at 0x02A0A990>]

不幸的是,它产生的生成器有些不稳定,因为输出将取决于你如何使用它们:

>>> gens = ((x for x in range(10) if x%2==i) for i in range(2))
>>> for g in gens:
        print(list(g))

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

[1, 3, 5, 7, 9]
[1, 3, 5, 7, 9]

我的建议是完整地写出生成器函数:我认为试图在 i 上获得正确的作用域而不这样做几乎是不可能的。

【讨论】:

  • 为什么将生成器包装在list() 中会导致它从 1 而不是 0 开始? (我用更多的生成器对其进行了测试,看起来生成器都在做同样的事情)
  • 当使用生成器时,它们总是做同样的事情:它们给你的数字是x%2==i。第一种方法将i 设置为0,然后生成一个生成器,用于给出偶数,然后i 变为1,另一个生成器返回给你奇数,N.B.每次都是相同的i,只是值不同。使用 list(gens) 意味着两个生成器都已创建,因此在您使用任一生成器之前,i 已设置为 1
  • 为什么第一种情况下i不共享?
  • 哦。我认为i 总是共享的,但在第一种情况下,gens 与外循环一起运行,而在第二种情况下,gens 在外循环完成后运行,因此它们都得到 i 的最终值。为什么i 不是副本,即生成器的作用类似于闭包?
【解决方案4】:

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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-20
    • 2018-09-08
    相关资源
    最近更新 更多