2012-07-10 152 views
7

下面是我從某人的博客關於python關閉的示例。 我在python 2.7中運行它,並得到與我的期望不同的輸出。關於python關閉

flist = [] 

for i in xrange(3): 
    def func(x): 
     return x*i 
    flist.append(func) 

for f in flist: 
    print f(2) 

我的預期輸出是:0,2,4
但輸出爲:4,4,4
是否有任何人可以幫助解釋呢?
預先感謝您。

+3

[Python中的詞彙關閉]的可能重複(http://stackoverflow.com/questions/233673/lexical-closures-in-python) – BrenBarn 2012-07-10 07:32:33

回答

16

循環不Python中介紹的範圍,因此,所有三個功能關閉在同一i變量,將引用循環結束後其最終價值,這是2

它好像幾乎每個人我與誰在Python中使用閉包交談已經被這個咬傷了。推論是外部函數可以改變i,但內部函數不能(因爲這會使得i成爲本地的,而不是基於Python的語法規則的閉包)。

有解決這個方法有兩種:

# avoid closures and use default args which copy on function definition 
for i in xrange(3): 
    def func(x, i=i): 
     return x*i 
    flist.append(func) 

# or introduce an extra scope to close the value you want to keep around: 
for i in xrange(3): 
    def makefunc(i): 
     def func(x): 
      return x*i 
     return func 
    flist.append(makefunc(i)) 

# the second can be simplified to use a single makefunc(): 
def makefunc(i): 
    def func(x): 
     return x*i 
    return func 
for i in xrange(3): 
    flist.append(makefunc(i)) 

# if your inner function is simple enough, lambda works as well for either option: 
for i in xrange(3): 
    flist.append(lambda x, i=i: x*i) 

def makefunc(i): 
    return lambda x: x*i 
for i in xrange(3): 
    flist.append(makefunc(i)) 
+0

+1偉大的解釋和解決方案。 – jamylak 2012-07-10 07:39:42

+0

其他讀者請注意:Python 3添加了'nonlocal'關鍵字,它允許每個'func'改變'i'的值,這反過來會影響其他讀者。在這種情況下沒用,但如果你有幾個內部函數,可能會很方便。 – 2012-07-10 09:34:08

+0

你可以用lambda簡化最後一個,儘管這限制了func()的功能。 – Dubslow 2012-07-10 09:36:12

4

您未創建閉包。您正在生成一個函數列表,每個函數在第一個循環後訪問等於2的全局變量i。因此,每個函數調用都以2 * 2結束。

+0

謝謝大家了回答。如果我想創建閉包來獲得我的預期輸出,如何更改代碼?你能給我一些建議嗎? – 2012-07-10 07:34:07

+0

@ Alex.Zhang:嗯,你確實要求解釋爲什麼這種行爲與你預期的不一樣。請參閱http://stackoverflow.com/a/11408601/21945獲取解決方案。 – mhawke 2012-07-10 07:38:35

1

每個函數訪問全球i

functools.partial來搶救:

from functools import partial 
flist = [] 

for i in xrange(3): 
    def func(x, multiplier=None): 
     return x * multiplier 
    flist.append(partial(func, multiplier=i))