2012-04-06 115 views
1

我的意图是创建一个字典,其中包含哪些键是基元,其值是否为返回字符串的零参数函数。 (这是实施VM的较大项目的一部分。)这些功能中的某些功能并不重要,并且可以手动创建和分配。那些工作很好。然而,其他人似乎适合自动生成。如何强制python3传递值?

我第一次尝试失败:

>>> 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')] 

OK,罚款; 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。深度复制不能解决这个问题。结果,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的原因之一是我远离手动指针管理;我从未想到我希望它包含一整套指针工具!

回答

2

Closures 从来没有副本,他们复制既没有值也没有引用。相反,他们记得他们使用的范围和变量,并且总是回到那里。其他几种语言也是如此,例如C#,JavaScript,IIRC Lisp等等。其他几种语言也是如此。这对于一些高级用例很重要(基本上每次你想要几次关闭共享状态),但它可以咬一口。例如:

x = 1 
def f(): return x 
x = 2 
assert f() == 2 

对于Python只创造了新的功能范围(和类和模块,但此处无关紧要),循环变量reg只存在一次,因此所有的封闭是指同一个变量。因此,在循环之后调用时,它们会看到变量假定的最后一个值。

同样如此,只有这一次是共享的t - 您可以创建N个单独的闭包,每个闭包的r属性中都有正确的值。但是当被调用时,他们在封闭范围内查找t,因此总是得到您创建的最后一个闭包的引用。

有几种解决方法。使用的事实,默认(AB-)

def make_const(x): 
    def const(): 
     return x 
    return const 

另一种可能性(更简洁,但更晦涩):一是推动建立封闭到一个单独的功能,这迫使一个新的专用范围为每封指参数在定义时的约束:

for reg in regs: 
    t = lambda reg=reg: reg 

在其他情况下,你可以使用functools,但它似乎并不适用于此。

+0

谢谢!设置默认参数对于我来说比通过make_const函数提供所有内容更重要,至少就我对它的工作原理的理解而言。我不认为这两种方法在性能上有任何显着差异? – coriolinus 2012-04-06 12:08:07

+0

@coriolinus不应有任何显着差异。如果有的话,在成为瓶颈之前可能有很多大的鱼要炒。在这一点上,你可能应该咬紧牙关,让你的代码[RPython](http://morepypy.blogspot.de/2011/04/tutorial-writing-interpreter-with-pypy.html),然后编译C,一个JIT编译器和一系列好的GC几乎免费。 – delnan 2012-04-06 12:37:50