閉包
關於Python閉包可參看另一篇文章《Python閉包》。
延遲綁定
由於閉包函數返回了內部函數的引用,外函數調用結束時會將其作用域內、被內函數引用的局部變量(亦稱爲閉包變量)綁定至內函數。延遲綁定是指只有在調用內函數時,纔會訪問閉包變量所指向的對象,不調用時不會訪問閉包變量所指向的對象。下面是網上比較流行的一個案例:
def multipliers():
return [lambda x : i*x for i in range(4)]
print ([m(2) for m in multipliers()] ) #將輸出[6, 6, 6, 6]
期望輸出[0, 2, 4, 6],但爲何會輸出[6, 6, 6, 6]?我們先將上述代碼翻譯一下:
def multipliers():
result = []
for i in range(4):
def func(x):
return i * x
result.append(func)
return result
tmp = []
for m in multipliers():
tmp.append(m(2))
print tmp #將輸出[6, 6, 6, 6]
不難看出,這是一個典型的閉包案例。該閉包返回了四個內部函數的引用,每個內部函數都綁定了閉包變量i。根據延遲綁定的定義,只有當調用內部函數時,纔會訪問閉包變量,當執行語句m(2)時,會調用內部函數func,此時纔會訪問變量i,此時變量i所指向的對象爲不可變對象整數3,而x的值是2,因此m(2)將返回6。調用4次即返回四個6,因此輸出爲[6, 6, 6, 6]。要怎麼才能輸出期望的值[0, 2, 4, 6]呢,有兩種方法:立即綁定、偏函數。
立即綁定
爲了解決延遲綁定帶來的問題,在生成閉包函數的時候可採用立即綁定,即使用函數形參的默認值:
def multipliers():
return [lambda x, i=i: i* x for i in range(4)]
print ([m(2) for m in multipliers()] ) #將輸出[0, 2, 4, 6]
multipliers函數返回四個內部函數對象,每個函數都有兩個參數:x和i,這四個函數對象的參數i指向了不同的不可變對象,分別是整數0、1、2、3。因此調用m(2)時,x的值爲2,得到的輸出結果自然就是[0, 2, 4, 6]。
偏函數
偏函數是2.5版本以後引進來的東西。屬於函數式編程的一部分,使用偏函數可以通過有效地“凍結”那些預先確定的參數,來緩存函數參數,然後在運行時,當獲得需要的剩餘參數後,可以將他們解凍,傳遞到最終的參數中,從而使用最終確定的所有參數去調用函數。偏函數相當於爲函數的部分參數提供了一個固定的默認值。
在有可能因爲延遲綁定而出問題的時候, 也可以通過functools.partial
構造偏函數, 使得自由變量優先綁定到閉包函數上,避免延遲綁定帶來的問題。
def multipliers():
return [functools.partial(lambda i, x: x * i, i) for i in range(4)]
print [m(2) for m in multipliers()] #輸出[0, 2, 4, 6]