Python閉包之延遲綁定

閉包

關於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]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章