2011-07-18 58 views
3

我試圖從函數返回一個函數列表,每個函數都使用來自外部作用域的變量。這不起作用。下面是一個演示發生了什麼的示例:有Python 2.7函數記住值而不是引用? Closure Weirdness

a = [] 
for i in range(10): 
    a.append(lambda x: x+i) 
a[1](1) # returns 10, where it seems it should return 2 

爲什麼會發生這種情況,以及如何在python 2.7中解決它?

回答

11

i每次都指向相同的變量,所以i在所有lambda表達式中都是9,因爲這是循環結束時的值i。簡單的解決方法涉及到一個默認參數:

lambda x, i=i: x+i 

這在拉姆達的定義時結合循環的i一個局部變量i的價值。

另一個解決方法是定義一個lambda定義另一個lambda,並調用第一拉姆達:

(lambda i: lambda x: x+i)(i) 

此行爲使更多一點意義,如果你考慮這個問題:

def outerfunc(): 

    def innerfunc(): 
     return x+i 

    a = [] 
    for i in range(10): 
     a.append(innerfunc) 
    return a 

這裏, innerfunc定義了一次,因此直觀地意識到你只使用一個函數對象,並且你不希望循環創建十個不同的閉包。使用lambda它不會外觀就像函數只定義一次,它看起來像你每次在循環中定義它新鮮,但實際上它在功能上與長版本相同。

+0

呵呵。真的沒有辦法讓每個lambda持有自己的價值嗎?我認爲這是關閉的關鍵。 – Chironex

+0

Kudos!有意義,但很明顯... – mjv

+0

@Chironex,閉包是變量名稱不值 –

4

因爲當你定義匿名函數(lambda表達式)但是它被調用時我沒有得到評估。你可以在a[1](1)之前加del i來看到這個:你會在a[1](1)行上得到NameError: global name 'i' is not defined

你需要每一次i的值修正爲lambda表達式,像這樣:

a = [lambda x, i=i: x+i for i in range(10)] 
a[1](1) # returns 2 
2

另外一個更通用的解決方案 - 還沒有lambda表達式:

import operator 
from functools import partial 
a = [] 
for i in range(10): 
    a.append(partial(operator.add, i)) 
a[1][(1) # returns 2 

關鍵方面在這裏是functools.partial

+0

'partial(operator.add,i)'相當於'(lambda i:lambda * args,** kwargs:operator.add(i,* args,** kwargs))()'但這絕對更漂亮,在可能的情況下,內置函數比lambda表達式更好。但我不會說這沒有lambda表達式:如果操作非常複雜,它仍然會涉及某種類型的函數定義。 – agf

+0

我認爲最後一對括號中應該有'i'。至於lambdas:至少沒有(雙)lambdas構造;)* double *在那裏讓我真的不寒而慄:) – phant0m

+0

是的,錯過了一個我,但我不能再次編輯顯然? – agf

相關問題