Python lambda闭包范围

tac*_*ell 27 python lambda closures

我试图使用闭包来消除函数签名中的变量(应用程序将编写连接接口的Qt信号所需的所有函数,以控制大量参数到存储值的字典).

我不明白为什么使用lambda未包装在另一个函数中的情况会返回所有情况的姓氏.

names = ['a', 'b', 'c']

def test_fun(name, x):
    print(name, x)

def gen_clousure(name):
    return lambda x: test_fun(name, x)

funcs1 = [gen_clousure(n) for n in names]
funcs2 = [lambda x: test_fun(n, x) for n in names]

# this is what I want
In [88]: for f in funcs1:
   ....:     f(1)
a 1
b 1
c 1

# I do not understand why I get this
In [89]: for f in funcs2:
   ....:     f(1)
c 1
c 1
c 1
Run Code Online (Sandbox Code Playgroud)

Bre*_*arn 52

原因是闭包(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
Run Code Online (Sandbox Code Playgroud)

在第二个示例中,n作为参数传递给函数"锁定"n的当前值到该函数.如果要以这种方式锁定值,则必须执行此类操作.(如果它不能以这种方式工作,那么诸如全局变量之类的东西根本不起作用;在使用时必须查找自由变量.)

请注意,此行为与la​​mbdas无关.如果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
Run Code Online (Sandbox Code Playgroud)

在这里,内部lambda仍然查找n调用它时的值.但n它引用的不再是全局变量,而是封闭函数内的局部变量makeFunc.每次makeFunc调用时都会创建此局部变量的新值,并且返回的lambda会创建一个闭包,以"保存"对该调用有效的局部变量值makeFunc.因此,在循环中创建的每个函数都有自己的"私有"变量x.(对于这个简单的情况,这也可以使用lambda作为外部函数--- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]]---但这不太可读.)

请注意,您仍然必须将您n的参数作为参数传递,只是通过这样做,您不会将其作为参数传递给进入stuff列表的同一函数; 而是将它作为参数传递给辅助函数,该辅助函数创建您想要放入的函数stuff.使用这种双功能方法的优点是返回的函数是"干净的"并且没有额外的参数; 如果你包装接受大量参数的函数,这可能很有用,在这种情况下,记住n参数在列表中的位置可能会让人感到困惑.缺点是,这样做,制作函数的过程更复杂,因为你需要另一个封闭函数.

结果是有一个权衡:你可以使函数创建过程更简单(即,不需要两个嵌套函数),但是你必须使得结果函数更复杂(即,它有这个额外的n=n参数) .或者您可以使函数更简单(即,它没有n=n参数),但是您必须使函数创建过程更复杂(即,您需要两个嵌套函数来实现该机制).