測開之函式進階· 第6篇《閉包》

清菡發表於2020-12-31

堅持原創輸出,點選藍字關注我吧

作者:清菡
部落格:oschina、雲+社群、知乎等各大平臺都有。

由於微信公眾號推送改為了資訊流的形式,防止走丟,請給加個星標 ⭐,你就可以第一時間接收到本公眾號的推送!

目錄

  • 一、非閉包
  • 二、閉包
    • 1.閉包的概念
    • 2.閉包的作用
  • 三、函式的__closure__屬性

一、非閉包

見過了在函式中呼叫函式本身,在函式內部定義一個函式:

def func():
    print('-----func被呼叫--------')
    def count_book():
        print('這個是計算買書方式的函式')

# func()是在外面定義的,可以直接呼叫func()
func()

在外面可以呼叫裡面的函式嗎?

不可以。相對於外部而言,def count_book()這個函式名是區域性的,是函式內部的一個區域性變數,所以在外部是訪問不了函式內部的資料。

在函式內部可以訪問外面的,但是在函式外面是訪問不了裡面的。

在外面定義個函式:

def login():
    print('登入')

def func():
    login()
    print('-----func被呼叫--------')
    def count_book():
        print('這個是計算買書方式的函式')

# func()是在外面定義的,可以直接呼叫func()
func()

在函式裡面是可以呼叫的,因為def login()它是個全域性變數。

要想在外面呼叫裡面的def count_book()函式,有什麼辦法呢?

加個return,把這個函式給返回回來。接下來func()函式呼叫之後,它會有個返回值,返回值就是count_book()。用res接收下,接收到了之後,通過res()再呼叫。

呼叫方式一:

def login():
    print('登入')


def func():
    login()
    print('-----func被呼叫--------')

    def count_book():
        print('這個是計算買書方式的函式')
    return count_book


# func()是在外面定義的,可以直接呼叫func()
res = func()
res()

呼叫方式二:

def login():
    print('登入')


def func():
    login()
    print('-----func被呼叫--------')

    def count_book():
        print('這個是計算買書方式的函式')
    return count_book


# func()是在外面定義的,可以直接呼叫func()

# 方式二
func()()

# 方式一
# res = func()
# res()

以上程式碼不是閉包,只是符合閉包的前 2 個條件,不符合條件“內層函式對外部作用域有一個非全域性的變數引用”。

二、閉包

1.閉包的概念

一個完整的閉包須滿足以下 3 個條件:

  • 函式中巢狀了一個函式
  • 外層函式返回內層函式的變數名
  • 內層函式對外部作用域有一個非全域性的變數引用

num = 100是外層函式裡定義的一個變數,不是全域性變數。

以上,這種形式的函式被稱為閉包。

全域性變數:變數是定義在模組裡,哪個地方都可以用。

例如:

非全域性變數:

不帶引數的閉包:

def func():
    num = 100
    def count_book():
        print(num)
        print('這個是計算買書方式的函式')
    return count_book

帶引數的閉包:

def func(num):
    def count_book():
        print(num)
        print('這個是計算買書方式的函式')
    return count_book

# func()是在外面定義的,可以直接呼叫func()

# 方式二
# func()()

# 方式一
res = func(2020)
res()

雖然num不是在外接函式中定義的,但是通過函式引數傳進來的,傳到func()的名稱空間裡面,print(num)在內部是可以引用到func()名稱空間裡面的值的。

這裡的num不是全域性變數,它是func()名稱空間裡面的一個變數,一個資料,是通過引數func(2020)傳進來的。

這個也是閉包,也滿足閉包的三個條件。

2.閉包的作用

實現資料的鎖定,提高穩定性。

遞迴函式在函式呼叫的時候是這樣的:

遞迴呼叫原理圖

在一個函式裡面呼叫自身的時候,它又有塊區間放這個函式,它內部有塊又呼叫了,它會繼續在記憶體裡面把這個函式給存起來,繼續這樣遞迴下去,非常佔記憶體。

閉包,它沒有遞迴。

函式呼叫的執行機制:

定義函式的時候,執行檔案,Python 直譯器從上往下執行程式碼,檢測到def login()的時候,會在記憶體裡面找一塊地址,讓函式名指向這個地址。

當你在下面再次呼叫這個函式的時候,Python 直譯器直接執行這個記憶體地址裡面的程式碼,也就是函式內部的程式碼。

程式碼從上往下執行的時候,檢測到有個func(),來個地址把func()裡面的程式碼,拿到地址裡。下面呼叫的時候就相當於直接執行地址裡面的程式碼了。

從上往下執行,又檢測到一個函式count_book(),這個時候又會把這個函式名拿出來,然後再往下走,它直接返回了函式。

函式名拿出來之後,把這個函式名拿出來放到了這裡,這個時候會給它再畫出來一塊地址。然後讓這個count_book()函式指向這個地址,有在呼叫count_book()它的時候,會執行裡面的程式碼。

這裡沒有呼叫,把count_book()這個函式名返回出來了。

count_book()這個函式名是在func()的名稱空間裡面。呼叫的時候返回到res這個地方來了。返回到全域性變數裡來了,通過res來接收下,res其實又指向這塊記憶體地址了。在外面通過res呼叫的時候,就會執行這個記憶體地址裡面的程式碼。

程式碼中有傳入引數num,函式裡面引用外層的變數num,這個變數和它放在同一個空間裡面。

三、函式的__closure__屬性

每個函式裡面都有一個這樣的屬性:

res.__closure__

這個屬性儲存的是:當前的這個函式它裡面的程式碼以及這個函式對外部非全域性變數引用的一個資料。

當是閉包的時候,返回這樣一個結果:

返回一個物件。這裡儲存的就是 2020。

將程式碼修改一下:

def func(num,b):
    def count_book():
        print(num)
        print(b)
        print('這個是計算買書方式的函式')
    return count_book

res = func(2020,'qinghan')
print(res.__closure__)

閉包函式引用的非全域性變數,會儲存在這個函式自身的一個__closure__屬性裡面,當要用的時候,直接從屬性裡面拿就行了。

通過這種方式實現資料鎖定,提高資料的安全性。

閉包函式需要使用到外部變數,為了避免使用的外部的變數發生變化。內部所用到的外部的變數,給鎖定到閉包函式自身的__closure__屬性裡面。

這時候外部的環境發生任何變化,對它都是沒有影響的。同時也不會對外層的環境造成影響。

全域性變數的時候返回 None:

如果一個閉包裡面引用了全域性變數,那麼就不算閉包了,例如:

引用全域性變數了就沒辦法實現資料鎖定了。


公眾號清菡軟體測試首發,更多原創文章:清菡軟體測試 115+原創文章,歡迎關注、交流,禁止第三方擅自轉載。

相關文章