Python3 閉包及裝飾器

閉包也就是裝飾器的實現,其作用在於:
1、不改變原函數內代碼段;
2、不改變原函數名稱;
3、爲原函數增加新的功能,改變輸出結果等等。

本文涉及到以下3個知識點:
1、作用域LEGB法則(知道的可以跳過)
2、閉包
3、裝飾器

1、作用域LEGB法則

搜索變量名的作用域優先級:L局部 > E嵌套 > G全局 > B內置

L局部(local):def構建函數段中,定義的變量
E嵌套(enclosing):當前域該變量名無賦值操作,引用父級;反之引用當前
G全局(global):不在任何函數段內,寫在所有函數外邊,最外層的變量
B內置(built-in):系統__builtin__模塊中的變量

L和G不用說了,局部和全局,就是函數裏和函數外。
B內置,也不提了,一般碰不到。
主要說說E

def fa():
	a = 0
	def fb():
		print(a)
	fb()
	
fa()

fa()嵌套fb(),子函數中沒有對變量a進行賦值操作,也是a=xxx之類的。所以在fb()的L中沒有的時候,可以向上層也就是父級尋找,使用E作用域的中的變量。

假設這裏改了一句

def fa():
	a = 0
	def fb():
		print(a)
		a = 1 # 這裏加一句對a的賦值
	fb()
	
fa()

# 輸出結果
Traceback (most recent call last):
  File "test.py", line 5, in fb
    print(a)
UnboundLocalError: local variable 'a' referenced before assignment
[Finished in 0.2s with exit code 1]

你會發現在print(a)的時候,就出現了a未定義的錯誤。原因在於fb()的L局部作用域中,有對a進行了賦值這樣的操作,a不能使用父級的定義。而在打印輸出時,局部函數中a尚未定義,導致程序報錯。

那麼局部就不可以修改父級了麼?其實是可以的。
原理和global聲明變量,局部變全局是一個道理。

def fa():
	a = 0
	def fb():
		nonlocal a
		print(a)
		a = 1
	fb()

fa()

使用nonlocal聲明後,a的作用域就擴展到了父級,就又可以使用E嵌套作用域了。

2、閉包說明

首先以一段閉包實現的代碼爲例:

def fa():
	a = 0
	def fb():
		nonlocal a
		print(a)
		a = 1
	return fb

f = fa()
f()
f()

# 輸出結果
0
1

將閉包函數,之前的嵌套函數對比
發現代碼基本一致,只有最後的fb()變成了return fb

def fa():				# def fa():
	a = 0				# 	a = 0
	def fb():			# 	def fb():
		nonlocal a		#		nonlocal a
		print(a)		#		print(a)
		a = 1			#		a = 1
	return fb			#	fb()

這就導致程序沒有執行內部函數fb,而是將內部函數打包作爲輸出。(閉包執行流程可以看當前頁最下方的測試記錄)

所以通過f = fa()返回的函數對象f,其實執行的是,打包好的內部函數fb()的內容,就是下面這段

	def fb():
		nonlocal a
		print(a)
		a = 1

但它的特別之處在於:
f創建時所攜帶的父層變量a會一直存在於內存中,不會因爲函數執行完成而消失(除非你主動del f刪掉)

f = fa()
f()
f()

# 輸出結果
0
1

其結果分別爲0和1的原因在於:
第1次f(),讀取了f初始化時的a值進行輸出爲0,然後通過a=1a才變成1
第2次f(),因爲a已經變成1,所以a值輸出爲1,然後通過a=1a依舊是1

其創建特徵在於:函數嵌套,返回函數對象,返回那個函數引用了父層變量。
其使用特徵在於:保存局部信息不被銷燬。

3、裝飾器說明

說完了閉包,來說裝飾器。

首先寫一個閉包程序,
函數funca將輸入參數+1,作爲結果輸出。
閉包實現的是任意輸入函數,將其輸出結果求平方。
所以將funca打包成new_funca後,輸出結果變成了(1+1)2=4(1+1)^2=4

def build_bibao(f):
	def bibao(*args):
		result = f(*args)
		return result**2
	return bibao

def funca(num):
	return num+1

new_funca = build_bibao(funca)
print(new_funca(1))

# 輸出結果
4

那麼將上面代碼用@簡寫,其實就是裝飾器的實現。

def build_bibao(f):
	def bibao(*args):
		result = f(*args)
		return result**2
	return bibao

@build_bibao
def funca(num):
	return num+1

print(funca(1))

# 輸出結果
4

裝飾器實際上對輸入函數funca做了包裝,然後給你返回了包裝好的,附加了新功能(結果求平方)的funca

============================================================

下面是自己研究閉包時候,做的一些測試(只做記錄,可以忽略)

代碼如下:

def funx(x):
	return x*x
	
def build_bibao(func):
	def bibao(j):
		return func(j)
	return bibao
	
new_funx = build_bibao(funx)
new_funx(5)

添加打印輸出,確認其執行過程:

def funx(x):
	return x*x
	
def build_bibao(func):
	print(0,type(func),func)
	def bibao(j):
		print(3,type(j),j)
		print(4,type(func.__name__),func.__name__)
		print(5,type(func(j)),func(j))
		return func(j)
	print(1,type(bibao),bibao)
	return bibao
	
new_funx = build_bibao(funx)
print(2,type(new_funx),new_funx)
new_funx(5)

可以依照打印序號,確認執行順序和結果

0 <class 'function'> <function funx at 0x000002AA4EA72EA0>
1 <class 'function'> <function build_bibao.<locals>.bibao at 0x000002AA4EDD8730>
2 <class 'function'> <function build_bibao.<locals>.bibao at 0x000002AA4EDD8730>
3 <class 'int'> 5
4 <class 'str'> funx
5 <class 'int'> 25
[Finished in 0.2s]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章