1. 程式人生 > 其它 >python 函式進階-閉包函式

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