python 函式進階-閉包函式
閉包函式
什麼是閉包函式
如果內函式使用了外函式的區域性變數,並且外函式把內函式返回出來的過程叫做閉包,裡面的內函式是閉包函式。
# 外函式 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