调用动态引用函数的嵌套生成器表达式

问题描述

当在 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 上看到它,还没有用其他版本测试

解决方法

这是两件事的结果:

  • 生成器是惰性求值的,所以函数只在生成器被消耗时调用,
  • 名称在评估时解析,而不是在生成器创建时解析。

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

map 起作用的原因是因为它是一个函数,所以当您将 proc 作为参数传递时,它会在调用 proc 时收到 map 的值,它在循环中,而 proc 仍然可以引用 double 函数。

,

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

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

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

你可以用一个生成器函数做同样的事情。我尝试使用生成器 expression

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

这不起作用,因为 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,您可以更改第一个生成器中引用后面的值。

,

我发现使用 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 - 这种行为的原因是什么,这是否记录在某处?