动态函数创建和函数体评估

Dynamic function creation and function body evaluation

考虑以下 Python 3 条指令

res = []
for a in range(3) :
    res.append(lambda n : n + a)

目的是构建一个包含三个函数 res[0]res[1]res[2] 的列表 res,使得 res[i](n) return s n + i,对于 [0, 1, 2].

中的所有 i

尽管如此,还是得到了

>>> res[0](0)
2

而不是

>>> res[0](0)
0

一个也有

>>> res[1](2)
4

此行为的解释是,在示例的任何动态生成的匿名函数的主体中的表达式 n + a 中,符号 a 在函数创建时未被计算。求值在for语句的出口执行,解释了为什么所有函数res[0]res[1]res[2] return它们的参数值加上2è (because abrowsesrange(3)and2` 是它的最后一个值。

请注意,问题不在于我们使用了匿名函数。确实,说明

res = []
for a in range(3) :
    def f(n) :
        return n + a
    res.append(f)

导致相同的行为。

另请注意,我们可以通过使用Python的函数eval来满足上面列出的objective:

res = []
for a in range(3) :
    s = "lambda n : n + %s" %(a)
    res.append(eval(s))

诀窍在于res的每个函数的创建都考虑了a的动态值。

现在我的问题是

  1. 这是错误还是功能?
  2. 是否有其他方法不通过 eval 以获得预期的行为?

我不能和#1 说话...除了说我确定这是有意和可取的某些定义的有意和可取的行为

但是 WRT #2

res = []
for a in range(3) :
    res.append(lambda n,b=a : n + b)

res[0](0)

这将在 for 循环内(而不是在退出后)评估 a 作为默认的第二个参数 ...

很粗糙,这使得它对类似

的东西开放
res[0](0,8)

因为 closure 动态函数引用变量 a 并在执行时使用它在 for 循环结束时的最终值。

您可以通过将其设为具有默认值的函数参数来防止这种情况发生,这样就不需要在调用时提供它。每个动态函数定义时a的值将成为使用的值。这就是我的意思:

res = []
for a in range(3) :
    res.append(lambda n, a=a: n + a)

print(res[0](0))  # -> 0
print(res[1](2))  # -> 3

这也行,如果你不想使用 lambda:

>>> from functools import partial
>>> for a in range(3) :
    def f(a, n):
        return n + a
    f = partial(f, a)
    res.append(f)

>>> res[0](0)
0
>>> res[1](2)
3

这为您提供了只有一个参数的干净函数:

res = []
for a in range(3) :
    res.append((lambda a: lambda n: n+a)(a))

不过,这样做可能会提高可读性和效率:

def adder(amount):
    return lambda n: n + amount

res = []
for a in range(3) :
    res.append(adder(a))

还有,你的"The evaluation is performed at the exit of the for statement"是错误的。尝试在循环后打印,然后进一步增加 a,然后再次打印。您会看到这些函数现在使用进一步增加的 a 值(当然,仅适用于您的版本,不适用于我的版本)。那是因为您的函数没有自己的 a,而是使用相同的全局 a,并在调用它们时对其求值。