堅持原創輸出,點擊藍字關注我吧
作者:清菡
博客: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+原創文章,歡迎關注、交流,禁止第三方擅自轉載。