2012-04-06 34 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