類從被載入到虛擬機記憶體中開始,到卸載出記憶體截止,整個生命周期包括:載入、驗證、準備、解析,初始化、使用、卸載七個階段。其中驗證、準備、解析三個部分統稱為連接。 類初始化情況: 遇到new、getstatic、putstatic 或 invokestatic 這4條位元組碼指令時,如果沒有初始化,則需要 ...
閉包函數
什麼是閉包函數
如果內函數使用了外函數的局部變數,並且外函數把內函數返回出來的過程叫做閉包,裡面的內函數是閉包函數。
# 外函數 outer
def outer():
# 外函數變數 num
var = '外函數局部變數'
# 內函數 inner
def inner():
# 內函數使用了外函數的變數 num
print('內函數使用了:' + var)
# 外函數將使用了外函數的局部變數的內函數返回
return inner
# 返回出的結果就是內函數 inner,現在inner就是一個閉包函數
func = outer()
# 執行返回出的 inner 函數
func() # 內函數使用了:外函數局部變數
下麵是一個複雜的版本。
inner函數返回了函數x 和 y,x 和 y是外函數的內函數,雖然覆蓋了原有的外函數的局部變數,但是這兩個函數本質上還是外函數的佈局變數,所以外函數返回了inner,inner就是一個閉包函數。
inner返回了外函數的x和y函數,x和y函數都是用了外函數的內函數num3,外函數返回inner,inner返回了x和y,所以變相的就是外函數返回了x和y,所以x和y也是閉包函數。
# 外函數
def outer():
# 外函數的局部變數
x = 1
y = 2
num3 = 3
# 內函數 x 重名變數 x
def x():
# 調用修改了 變數 num3
nonlocal num3
num3 *= 10
print(num3)
# 內函數 y 重名變數y
def y():
# 調用修改了 變數num3
nonlocal num3
num3 += 10
print(num3)
# 內函數inner
def inner():
# 返回了同級內函數 x y
return x, y
# 外函數最終返回了 inner函數
return inner
判斷是否是閉包函數
方法 | 作用 |
---|---|
_closure_ | 獲取閉包函數使用的局部變數 |
cell_contents | 獲取單元格對象當中的閉包函數 |
_closure_
可以使用這個方法判斷一個函數是否是一個閉包函數,因為閉包函數必須要使用外函數的局部變數,如果返回None就說明這個函數不是閉包函數,如果返回的是一個元組,說明這是一個閉包函數,元組中有cell單元格對象,一個單元格對象表示這個閉包函數使用了幾個外函數的局部變數。
拿上述版本測試。
# 外函數
def outer():
# 外函數的局部變數
x = 1
y = 2
num3 = 3
# 內函數 x 重名變數 x
def x():
# 調用修改了 變數 num3
nonlocal num3
num3 *= 10
print(num3)
# 內函數 y 重名變數y
def y():
# 調用修改了 變數num3
nonlocal num3
num3 += 10
print(num3)
# 內函數inner
def inner():
# 返回了同級內函數 x y
return x, y
# 外函數最終返回了 inner函數
return inner
# 執行outer返回的結果是inner
func = outer() # func == inner
# 執行func返回的是 x y 函數
a, b = func()
# 使用__closure__測試這個幾個函數是否是閉包函數
print(outer.__closure__)
print(func.__closure__)
print(a.__closure__)
print(b.__closure__)
'''
結果:除了外函數outer之外都返回了cell對象,說明inner x y 都是閉包函數
None
(<cell at 0x0000022F246AECA8: function object at 0x0000022F2466C400>, <cell at 0x0000022F247F3558: function object at 0x0000022F24850730>)
(<cell at 0x0000022F245D8708: int object at 0x00000000542280B0>,)
(<cell at 0x0000022F245D8708: int object at 0x00000000542280B0>,)
'''
cell_contents
雖然用__closure__
獲取到了閉包函數使用的元素,但是是以cell單元格對象的形式展示的,我們並不能看出這個使用的 元素到底是什麼東西,可以使用cell_contents
查看。
# 外函數
def outer():
# 外函數的局部變數
x = 1
y = 2
num3 = 3
# 內函數 x 重名變數 x
def x():
# 調用修改了 變數 num3
nonlocal num3
num3 *= 10
print(num3)
# 內函數 y 重名變數y
def y():
# 調用修改了 變數num3
nonlocal num3
num3 += 10
print(num3)
# 內函數inner
def inner():
# 返回了同級內函數 x y
return x, y
# 外函數最終返回了 inner函數
return inner
# 執行outer返回的結果是inner
func = outer() # func == inner
# 使用__closure__返回了閉包函數使用的局部變數
tup = func.__closure__
# 使用 cell_contents 查看這些局部變數都是些什麼
res = tup[0].cell_contents
print(res)
res = tup[1].cell_contents
print(res)
'''
結果:可以看到inner 使用的局部變數使用外函數的內函數 x 和 y
None
<function outer.<locals>.x at 0x0000018D5A66C400>
<function outer.<locals>.y at 0x0000018D5A850730>
'''
閉包函數的特點
讓我們回憶一下,函數中創建的變數是一個什麼變數?是一個局部變數。
局部變數的生命周期是多久?是等局部作用結束之後就會被釋放掉。
如果內函數使用了外函數的局部變數,那麼這個變數就與閉包函數發生了綁定關係,就延長該變數的生命周期。實際上就是記憶體給它存儲了這個值,暫時不釋放。
下麵的例子中,我們調用了函數outer並賦予了參數val的值為10,但是outer執行完之後,outer的val並沒有被釋放,而是被閉包函數inner延長了生命周期,所以val可以一直在inner中按照調用outer函數的時候賦予的值10進行運算。
因為內函數inner使用了外函數outer的變數val,且outer返回了inner,所以inner是一個閉包函數。因為inner是一個閉包函數,當它調用outer的變數val時就會延長val的生命周期,val就不會隨著outer的調用結束而被釋放
而是存儲在了記憶體當中,當inner再次使用val時,val就會將值賦予inner。
def outer(val):
def inner(num):
return val + num
return inner
func = outer(10)
res = func(10)
print(res) # 20
res = func(20)
print(res) # 30
閉包函數的意義
閉包可以優先使用外函數中的變數,並對閉包中的值起到了封裝包保護的作用,使外部無法訪問。
我們做一個模擬滑鼠點擊的事件,可以看得出閉包函數封裝保護數據的作用。
現在只是一個普通的函數,它無法對我們使用的變數的數據進行保護,在全局中這個數據可以被隨意的修改。
# 不使用閉包,當函數中調用全局變數時,外部也可以控制變數
# 全局變數
num = 0
# 點擊事件
def click_num():
# 每執行一次數值 +1
global num
num += 1
print(num)
# 執行點擊事件
click_num() # 1
click_num() # 2
click_num() # 3
# 在全局重新定義了num的值,num的值就被徹底的改變了,但是我們的程式的數據本不該如此。
num = 1231231
click_num() # 1231232
click_num() # 1231233
click_num() # 1231234
現在使用閉包函數對數據進行封裝保護,就不能在全局中隨意的修改我們使用的數據。
# 我們將需要使用的數據放在外函數中,點擊事件作為內函數也放在外函數中,然後作為閉包返回。
def clickNum():
# 需要使用的數據
num = 0
# 內函數(真正執行點擊事件的函數)
def inner():
# 執行點擊事件
nonlocal num
num += 1
print(num)
# 作為閉包返回
return inner
# 返回閉包
click_num = clickNum() # # click_num == inner
# 執行點擊事件
click_num() # 1
click_num() # 2
click_num() # 3
# 全局中修改 num 的值
num = 123412341234
# 閉包函數對數據的封裝保護起到了作用
click_num() # 4
click_num() # 5
click_num() # 6