1.何為閉包
在百度百科里面,看到了這樣的定義:
閉包就是能夠讀取其他函數內部變量的函數。例如在javascript中,只有函數內部的子函數才能讀取局部變量,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上,閉包是將函數內部和函數外部連接起來的橋梁。
閉包包含自由(未綁定到特定對象)變量,這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。“閉包” 一詞來源於以下兩者的結合:要執行的代碼塊(由於自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境(作用域)。
從上面兩段話我們可以更好的理解:在一個外函數中定義了一個內函數,內函數里運用了外函數的臨時變量,並且外函數的返回值是內函數的引用。這樣就構成了一個閉包。
下面是一個閉包的小例子:
def outer(): a = 6 def inner(): b = 8 print(a) print(b) return inner if __name__ == '__main__': res = outer() res() 執行結果: >>>6 >>>8
我們可以看到,當調用外部函數outer()時,會返回一個內部函數的引用res,再通過res()執行了內部函數,返回打印結果6和8
這上面的例子中,a作為外部函數outer()的局部變量,其被分配的內存在外部函數執行后應被釋放,但在外部函數執行后,發現自己的局部變量將被內部函數引用,就把這個變量綁定給了內部函數,然后再自己結束。此處的outer()的局部變量a稱之為自由變量,因此,對於內部函數inner()而言,其自由變量和局部變量我們可以通過下面的命令得到:
>>> res.__code__.co_freevars ('a',) >>> res.__code__.co_varnames ('b',)
上面我們說到,外部函數的局部變量a被綁定到了內部函數,我們可以通過返回函數res的__closure__屬性找到它
>>> res.__closure__[0].cell_contents 6
2.閉包的變量
def outer(): a = 6 def inner(): b = 8 a += 1 print(a) print(b) return inner if __name__ == '__main__': res = outer() res() 執行函數會報錯:UnboundLocalError: local variable 'a' referenced before assignment
這個報錯的意思是內部函數引用的變量a在賦值前已經被引用,原因在於變量a是數字,不可變類型,a += 1相當於在內部函數中創建了局部變量a = a + 1,這樣a不再是自由變量,也不存在閉包。
這里我們可以嘗試下外部函數的局部變量為可變類型的情況:
def outer(): a = [1,2,3] def inner(): b = 8 a = [4,5] print(a) print(b) return inner if __name__ == '__main__': res = outer() res() 執行結果: [4, 5] 8
可以看到,當a為可變類型時,函數可以正確執行。那么當a為不可變類型但又想在內部函數中改變a的值該怎么做,答案是利用nonlocal關鍵字。
def outer(): a = 6 def inner(): nonlocal a b = 8 a += 1 print(a) print(b) return inner if __name__ == '__main__': res = outer() res() 執行結果: 7 8
3.閉包的應用
下面模擬一個nba球員信息的小例子
首先用普通函數實現:
class PlayerInfo(): def __init__(self, name): self.name = name def position(self, position): return "{} is {}".format(self.name, position) curry = PlayerInfo("Stephen Curry") print(curry.position("PG")) lbj = PlayerInfo("Lebron James") print(lbj.position("SF")) 執行后: Stephen Curry is PG Lebron James is SF
下面用閉包函數來實現
def get_name(name): def get_position(position): return "{} is {}".format(name,position) return get_position if __name__ == "__main__": player01 = get_name("Stephen Curry") print(player01("PG")) player02 = get_name("Lebron James") print(player02("SF"))
因此我們可以看出,對於實現少量方法的類,我們可以用閉包代替。