原因是闭包(lambdas 或其他)关闭名称,而不是值。当您定义lambda x: test_fun(n, x)
时, n 不会被评估,因为它在函数内部。它在调用函数时进行评估,此时存在的值是循环中的最后一个值。
您在一开始就说您想“使用闭包从函数签名中消除变量”,但实际上并不是这样。(不过,请参阅下面的一种可能满足您的方式,具体取决于您所说的“消除”。)定义函数时,不会计算函数体内的变量。为了让函数获取在函数定义时存在的变量的“快照”,您必须将变量作为参数传递。执行此操作的常用方法是给函数一个参数,其默认值是来自外部作用域的变量。看看这两个例子之间的区别:
>>> stuff = [lambda x: n+x for n in [1, 2, 3]]
>>> for f in stuff:
... print f(1)
4
4
4
>>> stuff = [lambda x, n=n: n+x for n in [1, 2, 3]]
>>> for f in stuff:
... print f(1)
2
3
4
在第二个示例中,n
作为参数传递给函数将 n 的当前值“锁定”到该函数。如果您想以这种方式锁定值,则必须执行此类操作。(如果它不这样工作,像全局变量这样的东西根本就不起作用;在使用时查找自由变量是必不可少的。)
请注意,此行为并非特定于 lambdas。def
如果您使用定义从封闭范围引用变量的函数,则相同的范围规则有效。
如果你真的想要,你可以避免向返回的函数添加额外的参数,但要这样做,你必须将该函数包装在另一个函数中,如下所示:
>>> def makeFunc(n):
... return lambda x: x+n
>>> stuff = [makeFunc(n) for n in [1, 2, 3]]
>>> for f in stuff:
... print f(1)
2
3
4
在这里,内部 lambda 仍然会查找n
调用时的值。但n
它所指的不再是全局变量,而是封闭函数内部的局部变量makeFunc
。每次makeFunc
调用时都会创建此局部变量的新值,并且返回的 lambda 会创建一个闭包,该闭包“保存”对该调用有效的局部变量值makeFunc
。因此,在循环中创建的每个函数都有自己的“私有”变量,称为x
. (对于这个简单的情况,这也可以使用外部函数的 lambda 来完成 --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]]
--- 但这不太可读。)
请注意,您仍然必须将 yourn
作为参数传递,只是通过这种方式,您不会将其作为参数传递给最终进入stuff
列表的同一函数;相反,你将它作为参数传递给一个帮助函数,该函数创建你想要放入的函数stuff
。使用这种双函数方法的优点是返回的函数是“干净的”并且没有额外的参数;如果您要包装接受大量参数的函数,这可能很有用,在这种情况下,记住n
参数在列表中的位置可能会变得混乱。缺点是,这样做的话,制作函数的过程比较复杂,因为你需要另一个封闭的函数。
结果是有一个折衷:您可以使函数创建过程更简单(即,不需要两个嵌套函数),但是您必须使生成的函数更复杂一些(即,它有这个额外的n=n
参数) . 或者您可以使函数更简单(即它没有n=
n 参数),但是您必须使函数创建过程更复杂(即,您需要两个嵌套函数来实现该机制)。