1. 程式人生 > >測開之函式進階· 第6篇《閉包》

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

### 堅持原創輸出,點選藍字關注我吧 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201231130035.png) 作者:清菡 部落格:oschina、雲+社群、知乎等各大平臺都有。 > 由於微信公眾號推送改為了資訊流的形式,防止走丟,請給加個星標 ⭐,你就可以第一時間接收到本公眾號的推送! # 目錄 - 一、非閉包 - 二、閉包 - 1.閉包的概念 - 2.閉包的作用 - 三、函式的__closure__屬性 ## 一、非閉包 見過了在函式中呼叫函式本身,在函式內部定義一個函式: ```PYTHON def func(): print('-----func被呼叫--------') def count_book(): print('這個是計算買書方式的函式') # func()是在外面定義的,可以直接呼叫func() func() ``` `在外面可以呼叫裡面的函式嗎?` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230162240.png) 不可以。相對於外部而言,`def count_book()`這個函式名是區域性的,是函式內部的一個區域性變數,所以在外部是訪問不了函式內部的資料。 **在函式內部可以訪問外面的,但是在函式外面是訪問不了裡面的。** 在外面定義個函式: ```PYTHON def login(): print('登入') def func(): login() print('-----func被呼叫--------') def count_book(): print('這個是計算買書方式的函式') # func()是在外面定義的,可以直接呼叫func() func() ``` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230163414.png) 在函式裡面是可以呼叫的,因為`def login()`它是個全域性變數。 `要想在外面呼叫裡面的def count_book()函式,有什麼辦法呢?` 加個`return`,把這個函式給返回回來。接下來`func()`函式呼叫之後,它會有個返回值,返回值就是`count_book()`。用`res`接收下,接收到了之後,通過`res()`再呼叫。 呼叫方式一: ```python def login(): print('登入') def func(): login() print('-----func被呼叫--------') def count_book(): print('這個是計算買書方式的函式') return count_book # func()是在外面定義的,可以直接呼叫func() res = func() res() ``` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230164811.png) 呼叫方式二: ```python def login(): print('登入') def func(): login() print('-----func被呼叫--------') def count_book(): print('這個是計算買書方式的函式') return count_book # func()是在外面定義的,可以直接呼叫func() # 方式二 func()() # 方式一 # res = func() # res() ``` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230165201.png) 以上程式碼不是閉包,只是符合閉包的前 2 個條件,不符合條件“內層函式對外部作用域有一個非全域性的變數引用”。 ## 二、閉包 ### 1.閉包的概念 一個完整的閉包須滿足以下 3 個條件: - 函式中嵌套了一個函式 - 外層函式返回內層函式的變數名 - 內層函式對外部作用域有一個非全域性的變數引用 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230172226.png) `num = 100`是外層函式裡定義的一個變數,不是全域性變數。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230172825.png) 以上,這種形式的函式被稱為閉包。 全域性變數:變數是定義在模組裡,哪個地方都可以用。 例如: ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230173657.png) 非全域性變數: ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230174340.png) 不帶引數的閉包: ```PYTHON def func(): num = 100 def count_book(): print(num) print('這個是計算買書方式的函式') return count_book ``` 帶引數的閉包: ```PYTHON 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.閉包的作用 **實現資料的鎖定,提高穩定性。** 遞迴函式在函式呼叫的時候是這樣的: ![遞迴呼叫原理圖](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230183434.png) 在一個函式裡面呼叫自身的時候,它又有塊區間放這個函式,它內部有塊又呼叫了,它會繼續在記憶體裡面把這個函式給存起來,繼續這樣遞迴下去,非常佔記憶體。 閉包,它沒有遞迴。 #### 函式呼叫的執行機制: 定義函式的時候,執行檔案,Python 直譯器從上往下執行程式碼,檢測到`def login()`的時候,會在記憶體裡面找一塊地址,讓函式名指向這個地址。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230185133.png) 當你在下面再次呼叫這個函式的時候,Python 直譯器直接執行這個記憶體地址裡面的程式碼,也就是函式內部的程式碼。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230191419.png) 程式碼從上往下執行的時候,檢測到有個`func()`,來個地址把`func()`裡面的程式碼,拿到地址裡。下面呼叫的時候就相當於直接執行地址裡面的程式碼了。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230194041.png) 從上往下執行,又檢測到一個函式`count_book()`,這個時候又會把這個函式名拿出來,然後再往下走,它直接返回了函式。 函式名拿出來之後,把這個函式名拿出來放到了這裡,這個時候會給它再畫出來一塊地址。然後讓這個`count_book()`函式指向這個地址,有在呼叫`count_book()`它的時候,會執行裡面的程式碼。 這裡沒有呼叫,把`count_book()`這個函式名返回出來了。 `count_book()`這個函式名是在`func()`的名稱空間裡面。呼叫的時候返回到`res`這個地方來了。返回到全域性變數裡來了,通過`res`來接收下,`res`其實又指向這塊記憶體地址了。在外面通過`res`呼叫的時候,就會執行這個記憶體地址裡面的程式碼。 程式碼中有傳入引數`num`,函式裡面引用外層的變數`num`,這個變數和它放在同一個空間裡面。 ## 三、函式的__closure__屬性 每個函式裡面都有一個這樣的屬性: `res.__closure__` **這個屬性儲存的是:當前的這個函式它裡面的程式碼以及這個函式對外部非全域性變數引用的一個數據。** ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230195538.png) 當是閉包的時候,返回這樣一個結果: ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230195921.png) 返回一個物件。這裡儲存的就是 2020。 將程式碼修改一下: ```PYTHON def func(num,b): def count_book(): print(num) print(b) print('這個是計算買書方式的函式') return count_book res = func(2020,'qinghan') print(res.__closure__) ``` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230200444.png) 閉包函式引用的非全域性變數,會儲存在這個函式自身的一個`__closure__`屬性裡面,當要用的時候,直接從屬性裡面拿就行了。 通過這種方式實現資料鎖定,提高資料的安全性。 閉包函式需要使用到外部變數,為了避免使用的外部的變數發生變化。內部所用到的外部的變數,給鎖定到閉包函式自身的`__closure__`屬性裡面。 這時候外部的環境發生任何變化,對它都是沒有影響的。同時也不會對外層的環境造成影響。 #### 全域性變數的時候返回 None: ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230202914.png) 如果一個閉包裡面引用了全域性變數,那麼就不算閉包了,例如: ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20201230203116.png) 引用全域性變量了就沒辦法實現資料鎖定了。 --- 公眾號**清菡軟體測試**首發,更多原創文章:清菡軟體測試 115+原創文章,歡迎關注、交流,禁止第三方擅自轉載。