理解裝飾器之前先要理解閉包
-
閉包
定義
:在一個函數內部的函數,同時內部函數又引用了外部函數的變量。
本質
:閉包是將內部函數和外部函數的執行環境綁定在一起的對象。
優點
:內部函數可以使用外部變量。
缺點
:外部變量一直存在於內存中,不會在調用結束後釋放,佔用內存。
作用
:實現python裝飾器。 -
閉包三要素:
– 必須有一個內嵌函數。
– 內嵌函數必須引用外部函數中變量。
– 外部函數返回值必須是內嵌函數。 -
閉包函數的調用方式:
變量 = 外部函數名(參數)
變量(參數) -
函數名變量存儲着函數內存地址
def test(): print("這是test函數") print(test) # <function test at 0x000001D2BE18C1E0> test() # 這是test函數
僞代碼示例:
def 外部函數名(參數):
外部變量
def 內部函數名(參數):
使用外部變量
# 僅返回內部函數名(內部函數的內存地址)
return 內部函數名
代碼示例:
def test(name: str):
age = 18
def my_test():
print("name:", name)
print("age:", age)
# 僅返回內部函數名(內部函數的內存地址)
return my_test
print(test) # test函數的內存地址
print(test(name="test")) # test函數內部函數my_test 函數的內存地址
result = test(name="test") # 使用變量接收test 函數返回的 my_test函數內存地址
result() # 相當於 my_test()
裝飾器
而裝飾器decorators,正式利用了閉包的特性
-
定義
在不改變原函數的調用以及內部代碼情況下,爲其添加新功能的函數。
-
僞代碼示例
def 函數裝飾器名稱(func): 需要添加的新功能 def 內嵌函數名(*args, **kwargs): 需要添加的新功能 return func(*args, **kwargs) return 內嵌函數名
-
裝飾器的使用
使用@符號,直接在函數上方添加裝飾器@函數裝飾器名稱 想要裝飾的函數()
-
本質:
使用“@函數裝飾器名稱”修飾原函數,等同於創建與原函數名稱相同的變量,關聯內嵌函數;故調用原函數時執行內嵌函數。
原函數名稱 = 函數裝飾器名稱(原函數名稱)
-
代碼示例
- 定義兩個普通函數,實現打招呼,與說再見功能
def say_hello(): print("你好") def say_goodbye(): print("再見")
- 由於需求變更,要求打印函數自身名字
def say_hello(): print(say_hello.__name__) print("你好") def say_goodbye(): print(say_goodbye.__name__) print("再見")
- 使用裝飾器理念,變換函數
# 提取處通用點: print(函數名.__name__), 改造爲裝飾器 def print_func_name(func): # 函數名作爲參數傳遞 def wrapper(*args, **kwargs): # 包裝函數 print("函數名爲:", func.__name__) # 提供新功能 func(*args, **kwargs) # 執行原函數 return wrapper # 返回包裝函數的內存地址 @print_func_name # 使用裝飾器,裝飾原函數 def say_hello(): print("你好") @print_func_name def say_goodbye(): print("再見") say_hello() # 執行函數 say_goodbye()
-
示例代碼解析
@print_func_name def say_hello(): print("你好") say_hello()
Step1 : 函數由上至下執行,遇到print_func_name, say_hello函數,在內存中開闢棧幀,存儲函數
Step2 : 程序遇到了執行函數say_hello(),調用棧幀中的的函數
Step3 : @方法使其變爲:say_hello = print_func_name(say_hello)
形式來執行函數
Step4 : 遇到wrapper 函數,又開闢一塊棧幀,接着return wrapper
Step5 :say_hello = wrapper
-->say_hello() => wrapper()
Step6 :執行 wrapper函數:print("函數名爲:", func.__name__)
,並且執行 傳遞的參數func,也就是say_hello,say_hello(*args, **kwargs)棧幀
: 每個棧幀對應着一個未運行完的函數。棧幀中保存了該函數的返回地址和局部變量
多重裝飾器
一個函數可以被多個裝飾器修飾, 多重裝飾器,即多個裝飾器修飾同一個函數對象
,且執行順序爲從近到遠
@裝飾器1
@裝飾器2
函數名()
執行順序爲 : 函數名= 裝飾器1( 裝飾器2(函數名) )
先執行 裝飾器2(函數名) 將返回結果作爲參數 傳遞爲裝飾器1,函數1 再將結果返回
來看一段僞代碼示例:
- 場景 : 編寫一函數視圖,必須登錄纔可以訪問該視圖,並且執行該視圖時,打印出該函數名稱
# 打印函數名稱
def print_func_name(func1):
def wrapper(*args, **kwargs):
print("函數名爲:", func.__name__)
func1(*args, **kwargs)
return wrapper # 返回包裝函數的內存地址
# 登錄要求裝飾器
def my_login_required(func2):
def decorated_view(*args, **kwargs):
print("my_login_required開始調用")
return func2(*args, **kwargs)
return decorated_view
@my_login_required
@print_func_name
def my_view():
print("視圖函數開始調用")
ps: 在編程時,通常需要對視圖函數做出權限管理,比如說需要先登錄才能訪問某視圖函數,不只是此函數需要此功能,其他函數也需要,因此提取出共同點變爲裝飾器,用時添加即可
問題思考:
前面提到過,
多重函數執行順序爲從近到遠
,要求是:先登錄才能訪問該函數
,那麼爲什麼@print_func_name
在@my_login_required
之前?而不是@print_func_name
@my_login_required
def my_view():
print(“視圖函數開始調用”)
捋順思路:
- 執行結果
my_login_required開始調用 函數名爲: my_view 視圖函數開始調用
- 執行過程
- 項目由上至下執行,開闢棧幀存儲函數
- 遇到真正函數執行體my_view(),開始執行函數
- 由於裝飾器存在,並且按照規則 從近到遠 來解析
- 所以先執行print_func_name
- func1 = my_view
- 開闢棧幀存儲wrapper函數
- return wrapper
- 將wrapper作爲參數,傳遞給my_login_required
- func2 = wrapper
- 開闢棧幀存儲decorated_view函數
- return decorated_view
- 此時my_view() = decorated_view(),執行decorated_view()
打印結果
: my_login_required開始調用- return wrapper(*args, **kwargs)
- 執行 wrapper(*args, **kwargs) 函數
打印結果
: 函數名爲: my_view- 執行wrapper(*args, **kwargs) 函數 函數中的func 1函數
- 由於func1 = my_view(),所以執行my_view()函數
打印結果
: 視圖函數開始調用
得出結論:
由先近後遠原則,函數先執行靠近被函數,並將返回結果傳遞給上一層裝飾器。
即:多重裝飾器存在時,功能實現順序: 先遠後近
多層函數裝飾器
來看示例:
- 普通裝飾器
def print_func_name(func):
def wrapper(*args, **kwargs):
print("函數名爲:", func.__name__)
func(*args, **kwargs)
return wrapper
- 多層裝飾器
# 3層函數裝飾器
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被調用")
return func(*args, **kwargs)
return in_test
return test
當普通裝飾器滿足不了需求時,需要多層裝飾器,對函數進一步封裝
代碼示例:
def print_func_name(func): # 函數名作爲參數傳遞
def wrapper(*args, **kwargs): # 包裝函數
print("函數名爲:", func.__name__) # 提供新功能
func(*args, **kwargs) # 執行原函數
return wrapper # 返回包裝函數的內存地址
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被調用")
return func(*args, **kwargs)
return in_test
return test
@print_func_name # 使用裝飾器,裝飾原函數
def say_hello():
print("你好")
@out_test() # 多層函數裝飾器調用
def say_goodbye():
print("再見")
say_hello() # 執行函數
say_goodbye()
結果:
函數名爲: say_hello
你好
in_test 被調用
再見
函數從上至下執行
@out_test()執行到此處,發現了函數調用 : out_test()
執行out_test(),return test
解析test函數,打印 test 被調用,將test視爲裝飾器函數,等待被調用
再看示例:
# 代碼上同,將say_goodbye函數改爲 多重 + 多層函數裝飾器
@print_func_name
@out_test() # 多層函數裝飾器調用
def say_goodbye():
print("再見")
結果如下:
函數名爲: in_test
in_test 被調用
再見
或者:
@out_test() # 多層函數裝飾器調用
@print_func_name
def say_goodbye():
print("再見")
運行結果:
in_test 被調用
函數名爲: say_goodbye
再見
由此可見:
多重 + 多層函數裝飾器存在時,將多層函數裝飾器的返回結果視爲普通裝飾器
讓我們來看看4層函數裝飾器
# 3層函數裝飾器
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被調用")
return func(*args, **kwargs)
return in_test
return test
# 4層裝飾器
def final_test(my_array: list):
return out_test(my_array)
@final_test(my_array=[1,2,3])
def say_goodbye():
print("再見")
say_goodbye()
原理也是類似:
先執行函數,將結果返回作爲普通裝飾器,繼續解析函數
寫在最後: 如果把3層裝飾器,寫爲普通的裝飾器會這樣?
3層裝飾器函數 test()
裝飾函數時寫爲 @test, [ ps :正確寫法: @test()
]
答案顯而易見
內部傳遞參數錯誤,報出missing required positional argument錯誤
或者
沒有由於裝飾器函數沒有的正確返回結果,被裝飾函數未執行
創作不易,如您感覺有收穫,點個讚唄!~