【问题标题】:Nesting generator expression calling a dynamically referenced function嵌套生成器表达式调用动态引用的函数
【发布时间】:2021-07-16 15:20:21
【问题描述】:

当在 Python 3 中动态嵌套生成器表达式时,当生成器表达式引用动态引用的函数时,我看到了一些非常奇怪的行为,我不知道如何解释。

这是一个非常简化的重现问题的案例:

double = lambda x: x * 2
triple = lambda x: x * 3
processors = [double, triple]

data = range(3)
for proc in processors:
    data = (proc(i) for i in data)

result = list(data)
print(result)
assert result == [0, 6, 12]

在这种情况下,我希望每个数字都乘以 6 (triple(double(x))),但实际上调用了 triple(triple(x))。我或多或少清楚,proc 在生成器表达式运行时指向 triple,无论它在生成器表达式创建时指向什么。

那么,(1) 这是预期的吗?有人可以指出 Python 文档中的一些相关信息或其他解释这一点的地方吗?

和 (2) 您能否推荐另一种嵌套生成器表达式的方法,其中每个级别都调用一个动态提供的可调用对象?

编辑:我在 Python 3.8.x 上看到它,还没有用其他版本测试过

【问题讨论】:

  • 前两行可能是def 语句,但仍然是单行语句:def double(x): return x * 2(都在一行上)。然后,您将获得具有名称的函数的优势(这有助于解释错误消息),更不用说它只是更惯用的了。 Lambda 用于在您使用它们的地方生成内联函数。如果你将它分配给一个变量,你就是better off making it a proper function definition
  • @CrazyChucky 好点,完全同意。我只在这个例子中使用了 lambda,我的真实代码使用了实际的命名函数。

标签: python generator-expression


【解决方案1】:

这不起作用,因为 proc 在循环的第二次迭代中被重新分配为 triple,这会更改第一次迭代的 proc 引用。通过展开循环并删除最后一个生成器,这更加明确:

double = lambda x: x * 2
triple = lambda y: y * 3

data = range(3)
proc = double
data = (proc(i) for i in data)

# Let's change `proc` to be triple
proc = triple

result = list(data)
print(result)
# [0, 3, 6]

数据增加了三倍,这意味着通过将 proc 重新分配为 triple,您可以更改第一个生成器中引用后面的值。

【讨论】:

    【解决方案2】:

    是的,这是意料之中的,而且你的理由是对的。

    由于生成器是惰性的,proc(i) 仅在请求时才被评估。这涉及评估 proci 然后。当你最终请求时,proc 已经是 triple,所以这就是被使用的。

    在这种特殊情况下,data = map(proc, data) 完成了这项工作。它之所以有效,是因为map 捕获并记住了proc,就像您调用map 时一样。

    你可以用生成器函数做同样的事情。我尝试使用生成器表达式,例如

    data = (p(i) for p in [proc] for i in data)
    

    但它以ValueError: generator already executing 失败。不过,这行得通:

    data = (lambda proc: (proc(i) for i in data))(proc)
    

    【讨论】:

      【解决方案3】:

      这是两件事的结果:

      • 生成器是惰性求值的,因此只有在使用生成器时才会调用函数,
      • 在评估时解析名称,而不是在创建生成器时解析。

      所以当你使用list(data)消费生成器时,名称proc指的是函数triple,并且两个生成器都调用名称绑定的函数proc,所以你得到triple两次.

      map 起作用的原因是因为它是一个函数,所以当你将proc 作为参数传递时,它会在调用map 时接收proc 的值,这是在循环中而@ 987654330@还是可以参考double函数。

      【讨论】:

        【解决方案4】:

        我发现使用map 确实 工作,就我而言,这是#2 的一个很好的答案:

        double = lambda x: print(x) or x * 2
        triple = lambda x: print(x) or x * 3
        processors = [double, triple]
        
        data = range(3)
        for proc in processors:
            data = map(proc, data)
        
        result = list(data)
        print(result)
        assert result == [0, 6, 12]
        

        不过,我很高兴知道 #1 - 这种行为的原因是什么?这是否记录在某个地方?

        【讨论】:

          猜你喜欢
          • 2010-12-09
          • 2017-05-08
          • 2017-05-24
          • 2015-08-24
          • 1970-01-01
          • 2013-06-17
          • 2013-09-17
          • 1970-01-01
          • 2015-01-01
          相关资源
          最近更新 更多