lambda 函数闭包捕获什么?[重复]
- 2024-11-18 08:41:00
- admin 原创
- 16
问题描述:
最近我开始研究 Python,发现闭包的工作方式有些奇怪。考虑以下代码:
adders = [None, None, None, None]
for i in [0, 1, 2, 3]:
adders[i] = lambda a: i+a
print adders[1](3)
它构建了一个简单的函数数组,这些函数接受单个输入并返回该输入加上一个数字。这些函数以for
循环方式构建,迭代器i
从 运行0
到3
。对于每个数字,lambda
都会创建一个函数来捕获i
并将其添加到函数的输入中。最后一行使用 作为参数调用第二个lambda
函数3
。令我惊讶的是,输出是6
。
我期望的是4
。我的理由是:在 Python 中一切都是对象,因此每个变量本质上都是指向它的指针。在为 创建 的lambda
闭包时i
,我期望它存储一个指向 当前指向的整数对象的指针i
。这意味着当分配一个新的整数对象时,它不应该影响先前创建的闭包。遗憾的是,在调试器中i
检查数组显示它确实会受到影响。所有函数都引用 的最后一个值,,这导致返回。adders
`lambdai
3adders[1](3)
6`
这让我对以下问题产生了疑问:
闭包到底捕获了什么?
有什么最优雅的方法可以说服
lambda
函数捕获当前值,i
以便在i
其值发生变化时不会受到影响?
有关该问题的更易于理解、更实用的版本,具体到使用循环(或列表推导、生成器表达式等)的情况,请参阅在循环(或推导)中创建函数(或 lambda)。这个问题的重点是理解 Python 中代码的底层行为。
如果您来这里是为了尝试解决在 Tkinter 中制作按钮的问题,请尝试在 tkinter 循环中创建按钮并传递命令参数以获得更具体的建议。
有关 Python如何实现闭包的技术细节,请参阅obj.__closure__ 中究竟包含什么? 。有关相关术语讨论,请参阅早期绑定和晚期绑定之间的区别是什么? 。
解决方案 1:
您可以使用具有默认值的参数强制捕获变量:
>>> for i in [0,1,2,3]:
... adders[i]=lambda a,i=i: i+a # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4
这个想法是声明一个参数(巧妙地命名为i
),并给它一个你想要捕获的变量的默认值(的值 i
)
解决方案 2:
闭包到底捕获了什么?
Python 中的闭包使用词法作用域:它们记住创建闭包变量的名称和作用域。但是,它们仍然是后期绑定:在使用闭包中的代码时查找名称,而不是在创建闭包时查找。由于示例中的所有函数都是在同一作用域中创建的,并使用相同的变量名,因此它们始终引用相同的变量。
至少有两种方法可以获得早期绑定:
最简洁但不完全等价的方法是Adrien Plisson 推荐的方法。创建一个带有额外参数的 lambda,并将额外参数的默认值设置为要保留的对象。
更详细但也更健壮的是,我们可以为每个创建的 lambda 创建一个新的作用域:
>>> adders = [0,1,2,3]
>>> for i in [0,1,2,3]:
... adders[i] = (lambda b: lambda a: b + a)(i)
...
>>> adders[1](3)
4
>>> adders[2](3)
5
这里的范围是使用一个新函数(为简洁起见,是另一个 lambda)创建的,该函数绑定其参数,并将要绑定的值作为参数传递。不过,在实际代码中,您很可能使用普通函数而不是 lambda 来创建新的范围:
def createAdder(x):
return lambda y: y + x
adders = [createAdder(i) for i in range(4)]
解决方案 3:
为了完整性,对您的第二个问题的另一个回答是:您可以在functools模块中使用partial。
按照 Chris Lutz 的建议,通过从运算符导入 add ,示例变为:
from functools import partial
from operator import add # add(a, b) -- Same as a + b.
adders = [0,1,2,3]
for i in [0,1,2,3]:
# store callable object with first argument given as (current) i
adders[i] = partial(add, i)
print adders[1](3)
解决方案 4:
考虑以下代码:
x = "foo"
def print_x():
print x
x = "bar"
print_x() # Outputs "bar"
我想大多数人都不会觉得这令人困惑。这是预期的行为。
那么,为什么人们认为在循环中执行时会有所不同呢?我知道我自己也犯了这个错误,但我不知道为什么。是循环吗?或者是 lambda?
毕竟,循环只是以下内容的较短版本:
adders= [0,1,2,3]
i = 0
adders[i] = lambda a: i+a
i = 1
adders[i] = lambda a: i+a
i = 2
adders[i] = lambda a: i+a
i = 3
adders[i] = lambda a: i+a
解决方案 5:
这是一个新示例,它突出显示了闭包的数据结构和内容,以帮助阐明何时“保存”封闭上下文。
def make_funcs():
i = 42
my_str = "hi"
f_one = lambda: i
i += 1
f_two = lambda: i+1
f_three = lambda: my_str
return f_one, f_two, f_three
f_1, f_2, f_3 = make_funcs()
闭包里有什么?
>>> print f_1.func_closure, f_1.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43
值得注意的是,my_str 不在 f1 的闭包中。
f2 的闭包里有什么?
>>> print f_2.func_closure, f_2.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43
注意(从内存地址来看),两个闭包都包含相同的对象。因此,您可以开始将 lambda 函数视为对范围的引用。但是,my_str 不在 f_1 或 f_2 的闭包中,i 也不在 f_3 的闭包中(未显示),这表明闭包对象本身是不同的对象。
闭包对象本身是同一个对象吗?
>>> print f_1.func_closure is f_2.func_closure
False
解决方案 6:
回答你的第二个问题,最优雅的方法是使用一个接受两个参数而不是数组的函数:
add = lambda a, b: a + b
add(1, 3)
但是,在这里使用 lambda 有点愚蠢。Python 为我们提供了模块operator
,它为基本运算符提供了一个函数接口。上面的 lambda 只是为了调用加法运算符而产生了不必要的开销:
from operator import add
add(1, 3)
我了解您正在尝试探索该语言,但我无法想象我会使用函数数组的情况,而 Python 的作用域怪异性会妨碍这一点。
如果需要,您可以编写一个使用数组索引语法的小类:
class Adders(object):
def __getitem__(self, item):
return lambda a: a + item
adders = Adders()
adders[1](3)
解决方案 7:
在函数中创建加法器来捕获值:
def create_adder(i):
return lambda a: i + a
if __name__ == '__main__':
adders = [None, None, None, None]
for i in [0, 1, 2, 3]:
adders[i] = create_adder(i)
print(adders[1](3))
解决方案 8:
理清范围的一种方法i
是在另一个范围(闭包函数)中生成 lambda,并将必要的参数交给它以生成 lambda:
def get_funky(i):
return lambda a: i+a
adders=[None, None, None, None]
for i in [0,1,2,3]:
adders[i]=get_funky(i)
print(*(ar(5) for ar in adders))
5 6 7 8
当然是给予。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件