【问题标题】:Generator expression evaluation with several ... for ... in ... parts带有几个 ... for ... in ... 部分的生成器表达式评估
【发布时间】:2017-01-25 21:44:54
【问题描述】:

问题:当 Python 看到这种表达方式时,它会在后台做什么?

sum(sum(i) for j in arr for i in j)

我的想法上面的表达方式有效。但正如Python's docs所写:

生成器表达式使用函数作用域实现

不要冗长:) 我有一个具有以下布局的数组(作为示例):

>>> arr = [
           [[1,2,3], [4,5,6]],
           [[7,8,9],[10,11,12]]
          ]

首先,我尝试将arr 的所有元素与以下表达式相加:

>>> sum(sum(i) for i in j for j in arr)
NameError: name 'j' is not defined

它引发NameError,但如果使用函数范围实现,为什么不UnboundLocalError: local variable 'j' referenced before assignmentfor ... in ... 从左到右或从右到左的评估规则是什么?这个生成器表达式的等效生成器函数是什么?


编辑

我明白了。感谢@vaultah 提供一些见解。在这种情况下,j 是发送到生成器表达式的参数:

>>> sum(sum(i) for i in j for j in arr)  # NameError

这就是为什么我得到这个奇怪的NameError


@Eric answer 显示生成器表达式:

>>> sum(sum(i) for j in arr for i in j)

相当于:

>>> def __gen(arr):
        for j in arr:
            for i in j:
                yield sum(i)

>>> sum(__gen(arr))

【问题讨论】:

  • 你的问题是关于“为什么我得到这个错误而不是另一个错误”?看来你想完成类似sum(item for grid in arr for row in grid for item in row) 的事情。但我认为这不是你真正要问的。
  • 您似乎在这里有一大堆不同的问题 - 您问题顶部的列表理解与其他地方的不同 - 这是故意的吗?
  • @TadhgMcDonald-Jensen 是的,我喜欢你的解决方案!!!
  • @Eric 关于生成器表达式只有一个问题:sum(sum(i) for j in arr for i in j) 它是如何工作的?在我的想法中,我只是试图解释,我做了哪些步骤来获得正确的答案,所以我意识到我不明白发生了什么。我也喜欢your answer,不知道为什么它被否决!

标签: python python-3.x scope nested generator


【解决方案1】:

无论是生成器还是列表推导,推导嵌套都是一样的。使用列表推导更容易看到发生了什么,这就是我将在下面的示例中使用的内容。

给定:

>>> arr
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]

您可以使用嵌套列表推导(或生成器)将 Ints 列表的列表展平 1 级:

>>> [e for sl in arr for e in sl]
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

在给定结构的情况下,您可以通过再次嵌套来完全展平(仅作为示例;有更好的方法来展平深度嵌套的列表):

>>> [e2 for sl2 in [e for sl in arr for e in sl] for e2 in sl2]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

由于 sum 采用可迭代对象,因此在您的示例中不需要第二次展平:

>>> [sum(e) for sl in arr for e in sl]
[6, 15, 24, 33]   # sum of those is 78...

理解的一般形式是:

[ expression      for a_variable in a_DEFINED_sequence     optional_predicate ]

您可以使用未定义的名称获得与嵌套理解中所见相同的 NameError

>>> [c for c in not_defined]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'not_defined' is not defined

所以您在sum(sum(i) for i in j for j in arr) 上看到的错误是因为j 尚未定义。理解从左到右、从内到外进行评估。 j 作为序列的定义在其尝试使用的右侧。

要将列表推导展开为嵌套循环,inner(或左手)部分变为 outer 循环:

for sl in arr:
    for sl2 in sl:
        for e in sl2:
           # now you have each int in the LoLoInts...
           # you could use yield e for a generator here

你的最后一个问题:为什么你会得到TypeErrorgen = (j for j in arr)

那个生成器表达式什么都不做。示例:

>>> [j for j in arr]
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]
>>> [j for j in arr] == arr
True

所以表达式(j for j in arr) 只是返回一个生成器超过arr

sum 也不知道如何添加它或arr:

>>> sum(arr)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'

由于您的示例中的 gen 返回相同的数据结构,所以这是您的错误。

修复它:

>>> gen=(e for sl in arr for e in sl)
>>> sum(sum(li) for li in gen)
78

【讨论】:

  • 谢谢!您写道:“嵌套列表理解(或生成器)中元素的顺序只是嵌套的语法;内部元素是具有更高优先级的元素。” 这是什么意思 - 更高优先级?在我看来,内部元素是迭代最慢的元素(实际上它是传递给 genexp 的参数)。
  • 我想更高的优先级是指第一个遇到和解析的。我将在编辑中澄清。
【解决方案2】:

当 Python 看到这种表达式时,它会在后台做什么?

 sum(sum(i) for j in array for i in j)

它变成了等价于:

def __gen(it):
    # "it" is actually in locals() as ".0"
    for j in it:
        for i in j:
            yield sum(i)
sum(__gen(iter(arr)))

请注意,__iter__ 和名称解析都发生在函数范围之外。这仅适用于第一个 for 循环

PEP 0289 更详细地解释了这一点

【讨论】:

  • 我认为 sum(__gen( arr )) 没有明确调用迭代器就足够了。
  • @godaygo;是的,但那是错误的。试试g = (x for x in 1)。如果iter 在生成器中,则不会引发错误
猜你喜欢
  • 2022-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多