淺談python閉包


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"))

因此我們可以看出,對於實現少量方法的類,我們可以用閉包代替。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM