首頁 > 軟體

python 函數進階之閉包函數

2022-04-18 22:02:23

閉包函數

什麼是閉包函數

如果內函數使用了外函數的區域性變數,並且外函數把內函數返回出來的過程叫做閉包,裡面的內函數是閉包函數。

# 外函數 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

到此這篇關於python 函數進階之閉包函數的文章就介紹到這了,更多相關python 閉包函數內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com