如何强制python3传递值?

时间:2012-04-06 10:46:16

标签: lambda python-3.x immutability

我的目的是创建一个字典,其中的键是基元,其值是返回字符串的零参数函数。 (这是实现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的原因之一是我远离手动指针管理;我从没想过我希望它包含一整套指针工具!

1 个答案:

答案 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,但这似乎不适用于此。