1

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

4

1 回答 1

2

闭包从不复制,它们既不复制值也不复制引用。相反,他们记住了他们使用的范围和变量,并且总是回到那里。在其他几种语言中也是如此,例如 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,但它似乎不适用于这里。

于 2012-04-06T11:24:56.350 回答