Python中後期綁定(late binding)是什麼意思?
def create_multipliers():
return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
print multiplier(2)
上面的例子中,是不是在定義create_multipliers()函數而不是調用它時完成了循環?
結果是:
8
8
8
8
8
為什麼設定一個默認參數就得到預期的結果?
def create_multipliers():
return [lambda x,i=i : i * x for i in range(5)]
for multiplier in create_multipliers():
print multiplier(2)
結果是:
0
2
4
6
8
太久沒有寫 Python 我還實驗了半天…本來還想黑,WTF Python…然後才想通是自己太生疏。
你定義一個函數,函數內的變數並不是立刻就把值綁定了,而是等調用的時候再查找這個變數,如圖,定義函數的時候沒有 foo 變數,但是仍然可以,只要調用的時候環境里有就行。
一個道理,在 for 裡面 i 的值是不斷改寫的,但是 lambda 裡面只是儲存了 i 的符號,調用的時候再查找。這就是你說的後期綁定。
為什麼你加了默認參數就成功了呢?因為在創建函數的時候就要獲取默認參數的值,放到 lambda 的環境中,所以這裡相當於存在一個賦值,從而 lambda 函數環境中有了一個獨立的 i。
最後,優雅的寫法是用生成器:for multiplier in (lambda x : i * x for i in range(5)):
print(multiplier(2))
這樣惰性求值就可以避免 i 的改寫。
或者:
def create_multipliers():
for i in range(5):
yield lambda x: i * x
for multiplier in create_multipliers():
print(multiplier(2))
其實我想說,這個問題的重點並不是中後期綁定,正確實現的閉包語言都應該是在形成閉包的時候保存詞法作用域而不是自由變數的拷貝,所以它們都可以算作是中後期綁定。這個問題的重點其實應該是Python的列表推導其實只形成了一個詞法作用域,因此產生的每個閉包保存的環境實際是一樣的,所以每個閉包的調用結果就一樣了,如果你在形成閉包的時候給他們手動製造一個詞法作用域就行了,比如用lambda表達式。
def create_multipliers():
return (lambda x : i * x for i in range(5))
for multiplier in create_multipliers():
print multiplier(2)
這樣就ok.
推薦閱讀: