Python lambda 闭包范围 [英] Python lambda closure scoping

查看:22
本文介绍了Python lambda 闭包范围的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

names = ['a', 'b', 'c']def test_fun(name, x):打印(名称,x)def gen_closure(名称):返回 lambda x: test_fun(name, x)funcs1 = [gen_clousure(n) for n in name]funcs2 = [lambda x: test_fun(n, x) for n in names]# 这就是我要的在 [88] 中:对于 funcs1 中的 f:....: f(1)1乙 11#我不明白为什么我会得到这个在 [89] 中:对于 funcs2 中的 f:....: f(1)111

解决方案

原因是闭包(lambda 或其他)关闭名称,而不是值.当您定义 lambda x: test_fun(n, x) 时,不计算 n,因为它在函数内部.它在函数被调用时被评估,此时存在的值是循环中的最后一个值.

您在开始时说要使用闭包从函数签名中消除变量",但实际上并没有那样工作.(不过,请参阅下文,了解一种可能让您满意的方式,具体取决于您所说的消除"是什么意思.)在定义函数时不会评估函数体内的变量.为了让函数获取在函数定义时存在的变量的快照",您必须将该变量作为参数传递.通常的做法是给函数一个参数,它的默认值是来自外部作用域的变量.看看这两个例子的区别:

<预><代码>>>>stuff = [lambda x: n+x for n in [1, 2, 3]]>>>对于 f 的东西:... 打印 f(1)444>>>stuff = [lambda x, n=n: n+x for n in [1, 2, 3]]>>>对于 f 的东西:... 打印 f(1)234

在第二个示例中,将 n 作为参数传递给函数将 n 的当前值锁定"到该函数.如果你想以这种方式锁定价值,你必须做这样的事情.(如果它不以这种方式工作,那么全局变量之类的东西将根本无法工作;在使用时查找自由变量是必不可少的.)

请注意,有关此行为的任何内容都不是特定于 lambda 的.如果您使用 def 定义从封闭范围引用变量的函数,则相同的范围规则也有效.

如果你真的想要,你可以避免在返回的函数中添加额外的参数,但要这样做,你必须将该函数包装在另一个函数中,如下所示:

<预><代码>>>>def makeFunc(n):... 返回 lambda x: x+n>>>stuff = [makeFunc(n) for n in [1, 2, 3]]>>>对于 f 的东西:... 打印 f(1)234

这里,内部 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 参数),但是您必须使函数创建过程更复杂(即,您需要两个嵌套函数来实现该机制).

I am trying to use closures to eliminate a variable from a function signature (the application is to make writing all the functions needed for connecting Qt signals for an interface to control a largish number of parameters to the dictionary that stores the values ).

I do not understand why the case of using the lambda not wrapped in another function returns the last name for all cases.

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

解决方案

The reason is that closures (lambdas or otherwise) close over names, not values. When you define lambda x: test_fun(n, x), the n is not evaluated, because it is inside the function. It is evaluated when the function is called, at which time the value that is there is the last value from the loop.

You say at the beginning that you want to "use closures to eliminate a variable from a function signature", but it doesn't really work that way. (See below, though, for a way that may satisfy you, depending on what you mean by "eliminate".) Variables inside the function body will not be evaluated when the function is defined. In order to get the function to take a "snapshot" of the variable as it exists at function-definition time, you must pass the variable as an argument. The usual way to do this is to give the function an argument whose default value is the variable from the outer scope. Look at the difference between these two examples:

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

In the second example, passing n as an argument to the function "locks in" the current value of n to that function. You have to do something like this if you want to lock in the value in this way. (If it didn't work this way, things like global variables wouldn't work at all; it's essential that free variables be looked up at the time of use.)

Note that nothing about this behavior is specific to lambdas. The same scoping rules are in effect if you use def to define a function that references variables from the enclosing scope.

If you really want to, you can avoid adding the extra argument to your returned function, but to do so you must wrap that function in yet another function, like so:

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

Here, the inner lambda still looks up the value of n when it is called. But the n it refers to is no longer a global variable but a local variable inside the enclosing function makeFunc. A new value of this local variable is created every time makeFunc is called, and the returned lambda creates a closure that "saves" the local variable value that was in effect for that invocation of makeFunc. Thus each function created in the loop has its own "private" variable called x. (For this simple case, this can also be done using a lambda for the outer function --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] --- but this is less readable.)

Notice that you still have to pass your n as an argument, it's just that, by doing it this way, you don't pass it as an argument to the same function that winds up going into the stuff list; instead you pass it as an argument to a helper function that creates the function you want to put into stuff. The advantage of using this two-function approach is that the returned function is "clean" and doesn't have the extra argument; this could be useful if you were wrapping functions that accepted a lot of arguments, in which case it could become confusing to remember where the n argument was in the list. The disadvantage is that, doing it this way, the process of making the functions is more complicated, since you need another enclosing function.

The upshot is that there is a tradeoff: you can make the function-creation process simpler (i.e., no need for two nested functions), but then you must make the resulting function a bit more complicated (i.e., it has this extra n=n argument). Or you can make the function simpler (i.e., it has no n=n argument), but then you must make the function-creation process more complicated (i.e., you need two nested functions to implement the mechanism).

这篇关于Python lambda 闭包范围的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆