我的目的是创建一个字典,其中的键是基元,其值是返回字符串的零参数函数。 (这是实现VM的更大项目的一部分。)这些功能中的某些功能非常重要,并且可以手动创建和分配。那些工作正常。然而,其他人似乎可以自动生成。
我的第一次尝试失败了:
>>> regs = ['a', 'b', 'c', 'x', 'y', 'z']
>>> vals = {i : lambda: r for i, r in enumerate(regs)}
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]
好的,好的; lambda函数在调用之前不会读取r。我再次尝试,试图单独隔离一个值:
>>> from copy import copy
>>> vals = {}
>>> i = 0
>>> for reg in regs:
... r = copy(reg) # (1)
... vals[i] = lambda: r
... i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]
(1)我认为这一步将创建一个自变量,当reg执行时不会改变。结果并非如此。
所以这种尝试显然不起作用。也许在字符串上复制是一个noop?
>>> 's' is 's'
True
>>> a = 's'
>>> b = copy(a)
>>> a is b
True
>>> from copy import deepcopy
>>> b = deepcopy(a)
>>> a is b
True
右。复制,在字符串上,是一个noop。 Deepcopy无法解决此问题。因此,lambda仍然具有对每个循环上正在更新的变量的引用,从而导致此错误。
我们需要一种不同的方法。如果我将我想要的变量保存到临时函数的静态变量怎么办?如果每个临时函数都有自己的身份,这应该有用......
>>> vals = {}
>>> i = 0
>>> for reg in regs:
... def t():
... return t.r
... t.r = reg
... vals[i] = t
... i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]
不。此时,我正处于手动处理它的边缘:
>>> vals = {}
>>> vals[0] = lambda: 'a'
>>> vals[1] = lambda: 'b'
......等等。但这感觉就像放弃了,并且会非常乏味。是否有适当的pythonic方法来完成这项任务?毕竟,我通常喜欢python的原因之一是我远离手动指针管理;我从没想过我希望它包含一整套指针工具!
答案 0 :(得分:2)
Closures 从不复制,它们既不复制值也不复制引用。相反,他们记住了他们使用的范围和变量,并且总是回到那里。在其他几种语言中也是如此,例如C#,JavaScript,IIRC Lisp等等。其他几种语言也是如此。这对于一些高级用例很重要(基本上每次你想要几个闭包来共享状态),但它可以咬一个。例如:
x = 1
def f(): return x
x = 2
assert f() == 2
由于Python只为函数(以及类和模块创建新的作用域,但这并不重要),循环变量reg
只存在一次,因此所有闭包都引用相同的变量。因此,在循环后调用时,它们会看到变量所采用的最后一个值。
同样如此,只有这一次是共享的t
- 你确实创建了N个单独的闭包,每个闭包在r
属性中都有正确的值。但是在调用时,它们在封闭范围内查找t
,因此总是获得对您创建的最后一个闭包的引用。
有几种解决方法。一个是将闭包创建推送到一个单独的函数中,这会强制每个闭包的新的专用范围引用:
def make_const(x):
def const():
return x
return const
另一种可能性(更严格但更模糊)是(ab-)使用默认参数在定义时绑定的事实:
for reg in regs:
t = lambda reg=reg: reg
在其他情况下,您可以使用functools
,但这似乎不适用于此。