python進階(12)閉包

Silent丿丶黑羽發表於2021-02-28

閉包

首先了解一下:如果在一個函式的內部定義了另一個函式,外部的我們叫他外函式,內部的我們叫他內函式。
在一個外函式中定義了一個內函式,內函式裡運用了外函式的臨時變數,並且外函式的返回值是內函式的引用。這樣就構成了一個閉包。
 

一般情況下,在我們認知當中,如果一個函式結束,函式的內部所有東西都會釋放掉,還給記憶體,區域性變數都會消失。但是閉包是一種特殊情況,如果外函式在結束的時候發現有自己的臨時變數將來會在內部函式中用到,就把這個臨時變數繫結給了內部函式,然後自己再結束。

#閉包函式的例項
# outer是外部函式 a和b都是外函式的臨時變數
def outer( a ):
    b = 10
    # inner是內函式
    def inner():
        #在內函式中 用到了外函式的臨時變數
        print(a+b)
    # 外函式的返回值是內函式的引用
    return inner

if __name__ == '__main__':
    # 在這裡我們呼叫外函式傳入引數5
    #此時外函式兩個臨時變數 a是5 b是10 ,並建立了內函式,然後把內函式的引用返回存給了demo
    # 外函式結束的時候發現內部函式將會用到自己的臨時變數,這兩個臨時變數就不會釋放,會繫結給這個內部函式
    demo = outer(5)
    # 我們呼叫內部函式,看一看內部函式是不是能使用外部函式的臨時變數
    # demo存了外函式的返回值,也就是inner函式的引用,這裡相當於執行inner函式
    demo() # 15

    demo2 = outer(7)
    demo2()#17

從上面例子是我寫的一個最簡單的很典型的閉包。我估計如果是初學的小夥伴,可能很多名詞都不明白是什麼意思,沒關係,我把這些名詞按照自己的理解去解釋一下~

外函式返回了內函式的引用

引用是什麼?在python中一切都是物件,包括整型資料1,函式,其實是物件。
 
當我們進行a=1的時候,實際上在記憶體當中有一個地方存了值1,然後用a這個變數名存了1所在記憶體位置的引用。引用就好像c語言裡的指標,大家可以把引用理解成地址。a只不過是一個變數名字,a裡面存的是1這個數值所在的地址,就是a裡面存了數值1的引用。
 
相同的道理,當我們在python中定義一個函式def demo(): 的時候,記憶體當中會開闢一些空間,存下這個函式的程式碼、內部的區域性變數等等。這個demo只不過是一個變數名字,它裡面存了這個函式所在位置的引用而已。我們還可以進行x = demo y = demo, 這樣的操作就相當於,把demo裡存的東西賦值給x和y,這樣x 和y 都指向了demo函式所在的引用,在這之後我們可以用x() 或者 y() 來呼叫我們自己建立的demo() ,呼叫的實際上根本就是一個函式,x、y和demo三個變數名存了同一個函式的引用
 
有了上面的解釋,我們可以繼續說,返回內函式的引用是怎麼回事了。對於閉包,在外函式outer中 最後return inner,我們在呼叫外函式 demo = outer() 的時候,outer返回了inner,inner是一個函式的引用,這個引用被存入了demo中。所以接下來我們再進行demo() 的時候,相當於執行了inner函式
 
同時我們發現,一個函式,如果函式名後緊跟一對括號,相當於現在我就要呼叫這個函式,如果不跟括號,相當於只是一個函式的名字,裡面存了函式所在位置的引用
 

外函式把臨時變數繫結給內函式

按照我們正常的認知,一個函式結束的時候,會把自己的臨時變數都釋放還給記憶體,之後變數都不存在了。一般情況下,確實是這樣的。但是閉包是一個特殊情況。外部函式發現,自己的臨時變數會在將來的內部函式中用到,自己在結束的時候,返回內函式的同時,會把外函式的臨時變數送給內函式繫結在一起。所以外函式已經結束了,呼叫內函式的時候仍然能夠使用外函式的臨時變數
 
在我編寫的例項中,我兩次呼叫外部函式outer,分別傳入的值是5和7。內部函式只定義了一次,我們發現呼叫的時候,內部函式是能識別外函式的臨時變數是不一樣的。python中一切都是物件,雖然函式我們只定義了一次,但是外函式在執行的時候,實際上是按照裡面程式碼執行的,外函式裡建立了一個函式,我們每次呼叫外函式,它都建立一個內函式,雖然程式碼一樣,但是卻建立了不同的物件,並且把每次傳入的臨時變數數值繫結給內函式,再把內函式引用返回。雖然內函式程式碼是一樣的,但其實,我們每次呼叫外函式,都返回不同的例項物件的引用,他們的功能是一樣的,但是它們實際上不是同一個函式物件
 

閉包中內函式修改外函式區域性變數

在閉包內函式中,我們可以隨意使用外函式繫結來的臨時變數,但是如果我們想修改外函式臨時變數數值的時候發現出問題了!
 
在基本的python語法當中,一個函式可以隨意讀取全域性資料,但是要修改全域性資料的時候有兩種方法:

  • 1 global 宣告全域性變數
  • 2 全域性變數是可變型別資料的時候可以修改

在閉包內函式也是類似的情況。在內函式中想修改閉包變數(外函式繫結給內函式的區域性變數)的時候:

  • 在python3中,可以用nonlocal 關鍵字宣告 一個變數, 表示這個變數不是區域性變數空間的變數,需要向上一層變數空間找這個變數。
  • 在python2中,沒有nonlocal這個關鍵字,我們可以把閉包變數改成可變型別資料進行修改,比如列表。
#修改閉包變數的例項
# outer是外部函式 a和b都是外函式的臨時變數
def outer(a):
    b = 10  # a和b都是閉包變數
    c = [a] #這裡對應修改閉包變數的方法2
    # inner是內函式
    def inner():
        #內函式中想修改閉包變數
        # 方法1 nonlocal關鍵字宣告
        nonlocal b
        b += 1
        # 方法二,把閉包變數修改成可變資料型別 比如列表
        c[0] += 1
        print(c[0])
        print(b)
    # 外函式的返回值是內函式的引用
    return inner

if __name__ == '__main__':

    demo = outer(5)
    demo() 
# 結果
6
11

從上面程式碼中我們能看出來,在內函式中,分別對閉包變數進行了修改,列印出來的結果也確實是修改之後的結果。以上兩種方法就是內函式修改閉包變數的方法。
 
還有一點需要注意:使用閉包的過程中,一旦外函式被呼叫一次返回了內函式的引用,雖然每次呼叫內函式,是開啟一個函式執行過後消亡,但是閉包變數實際上只有一份,每次開啟內函式都在使用同一份閉包變數

def outer(x):
    def inner(y):
        nonlocal x
        x += y
        return x
    return inner


a = outer(10)
print(a(1))
print(a(3))

# 結果
11
14

兩次分別列印出11和14,由此可見,每次呼叫inner的時候,使用的閉包變數x實際上是同一個。
參考資料:https://www.cnblogs.com/s-1314-521/p/9763376.html

相關文章