2009-01-17 172 views
24

我想從Python中的常量列表創建一個lambda對象列表;例如:如何創建Python lambdas列表(在列表理解/ for循環中)?

listOfNumbers = [1,2,3,4,5] 
square = lambda x: x * x 
listOfLambdas = [lambda: square(i) for i in listOfNumbers] 

這將創建拉姆達對象的名單,但是,當我運行它們:

for f in listOfLambdas: 
    print f(), 

我希望它會打印

1 4 9 16 25 

相反,它打印:

25 25 25 25 25 

似乎所有的lambda都被賦予了錯誤的參數。我做錯了什麼,有沒有辦法解決它?我在想Python 2.4。

編輯:更多的嘗試新事物了一下,這樣的想出了這一點:

listOfLambdas = [] 
for num in listOfNumbers: 
    action = lambda: square(num) 
    listOfLambdas.append(action) 
    print action() 

打印預期廣場從1到25,但是如果使用較早的print語句:

for f in listOfLambdas: 
    print f(), 

仍然給我所有的25 s。這兩個打印調用之間現有的lambda對象是如何變化的?

相關問題:Why results of map() and list comprehension are different?

回答

17

我猜你創建的列表理解拉姆達被綁定到變量i最終在5結束了。因此,當你在事後評估lambda表達式,他們都被綁定到5並最終計算25.同樣的事情發生在第二個例子中的num。當你在循環中評估lambda時,它的num沒有改變,所以你得到正確的值。循環後,num是5 ...

我不太清楚你要做什麼,所以我不知道如何提出解決方案。這個怎麼樣?

def square(x): return lambda : x*x 
listOfLambdas = [square(i) for i in [1,2,3,4,5]] 
for f in listOfLambdas: print f() 

這給了我所期望的輸出:

1 
4 
9 
16 
25 

另一種方式去思考的,這是一個拉姆達「捕獲」它在那裏創建點詞法環境。所以,如果你給它num它實際上並沒有解決這個值,直到它被調用。這既困惑又強大。

0

我有時會發現定義函數對象的實際類可以更容易地理解這是怎麼回事:

>>> class square(object): 
... def __init__(self, val): 
...  self.val = val 
... def __call__(self): 
...  return self.val * self.val 
... 
>>> l = [1,2,3,4,5] 
>>> funcs = [square(i) for i in l] 
>>> for f in funcs: 
... print f() 
... 
1 
4 
9 
16 
25 
>>> 

誠然,這比使用Lambda表達式或倒閉更詳細一點,但我覺得這是比較容易理解當我試圖用函數做非顯而易見的事情時。

18

您有:

listOfLambdas = [lambda: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

輸出:

25 
25 
25 
25 
25 
25 

你需要鑽營!除了美味之外,使用這個默認值「hack」。

listOfLambdas = [lambda i=i: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

輸出:

0 
1 
4 
9 
16 
25 

注意i=i。這就是魔術發生的地方。

+2

很酷。這是「黑客」記錄在任何地方?有沒有更好的方法來做咖喱? 另外,請不要再提你的旋轉牀。 – 2009-01-17 02:02:50

2
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers] 

或者

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers) 
3

當函數語句執行就必然要他們(詞彙)封閉的範圍。

在您的代碼片段中,lambda表達式綁定到全局作用域,因爲for套件不作爲Python中的獨立作用域單元執行。在for循環結束時,num被綁定在封閉範圍內。演示:

for num in range(1, 6): 
    pass 
assert num == 5 # num is now bound in the enclosing scope 

因此,當您在for循環中綁定標識符時,您實際上正在操作封閉範圍。

for num in range(1, 6): 
    spam = 12 
assert num == 5 # num is now bound in the enclosing scope 
assert spam == 12 # spam is also bound in the enclosing scope 

同樣的協議爲列表解析:

[num for num in range(1, 6)] 
assert num == 5 

令人興奮的,我知道。任何人,憑藉我們新發現的知識,我們可以確定您創建的lambda表達式是指在封閉範圍內綁定的(單個)標識符。這應該使這個更有意義:

functions = [] 
for number in range(1, 6): 
    def fun(): 
     return number 
    functions.append(fun) 
assert all(fun() == 5 for fun in functions) 
assert all(fun() is number for fun in functions) 

這裏是一個演示它更最酷的部分:

# Same as above -- commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# def fun(): 
#  return number 
# functions.append(fun) 
#assert all(fun() == 5 for fun in functions) 
#assert all(fun() is number for fun in functions) 
number = 6 # Rebind 6 in the scope and see how it affects the results. 
assert all(fun() == 6 for fun in functions) 

所以解決這一點,當然,是讓一個新的封閉範圍爲您要綁定的每個number。在Python中,您可以使用模塊,類和函數創建新的封閉範圍。通常只使用函數爲另一個函數創建新的封閉作用域。

在Python中,一個關閉是一個函數,返回另一個函數。有點像函數構造函數。在下面的例子中檢查出get_fun

def get_fun(value): 
    """:return: A function that returns :param:`value`.""" 
    def fun(): # Bound to get_fun's scope 
     return value 
    return fun 

functions = [] 
for number in range(1, 6): 
    functions.append(get_fun(number)) 
assert [fun() for fun in functions] == range(1, 6) 

由於get_fun是一個函數,它就會擁有自己的內部範圍。每次您用值調用get_fun時,都會創建一個小表來跟蹤其中的綁定;即它說:「在這個範圍內,標識符指向已通過的事物。」這個範圍在函數執行結束時會消失,除非有理由讓它停下來。

如果你從一個範圍內返回一個函數,那麼這就是「範圍表」部分存在的一個很好的原因 - 當你調用它時,你返回的函數可以引用該範圍表中的東西稍後的。因此,在get_fun內創建fun時,Python會告訴funget_fun的範圍表,其中fun在需要時會保持方便。

您可以在Python docs on the execution model中閱讀更多關於細節和技術術語(我稍微軟化了一下)的內容。您也可以通過print fun.__closure__查看函數引用的封閉範圍的部分。在上文中,我們看到了參考value,這恰好是一個int:不是[])

# Same as before, commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# functions.append(get_fun(number)) 
#assert [fun() for fun in functions] == range(1, 6) 
print functions[0].__closure__ 
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,) 
1

嘗試使用(:

listOfLambdas = (lambda: square(i) for i in listOfNumbers) 

,你會得到:

1 
4 
9 
16 
25 
0

你也可以這樣做:

>>> def squares(): 
...  for i in [1,2,3,4,5]: 
...   yield lambda:i*i 
... 
>>> print [square() for square in squares()] 
[1, 4, 9, 16, 25] 
0

作爲額外的評論,我想概述一下從sympy矩陣生成lambda函數列表的可能性(我不知道這是否是最好的方法,但這是我的工作方式,我覺得它很方便) :

import sympy as sp 
sp.var('Ksi') 
# generate sympy expressions for Berstein's polynomials 
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)]) 
# lambdify them 
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]