python之裝飾器,多重裝飾器,多層函數裝飾器

理解裝飾器之前先要理解閉包

  • 閉包

    定義:在一個函數內部的函數,同時內部函數又引用了外部函數的變量。
    本質:閉包是將內部函數和外部函數的執行環境綁定在一起的對象。
    優點:內部函數可以使用外部變量。
    缺點:外部變量一直存在於內存中,不會在調用結束後釋放,佔用內存。
    作用:實現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
    視圖函數開始調用
    
  • 執行過程
    1. 項目由上至下執行,開闢棧幀存儲函數
    2. 遇到真正函數執行體my_view(),開始執行函數
    3. 由於裝飾器存在,並且按照規則 從近到遠 來解析
    4. 所以先執行print_func_name
      • func1 = my_view
      • 開闢棧幀存儲wrapper函數
      • return wrapper
    5. 將wrapper作爲參數,傳遞給my_login_required
      • func2 = wrapper
      • 開闢棧幀存儲decorated_view函數
      • return decorated_view
    6. 此時my_view() = decorated_view(),執行decorated_view()
      • 打印結果 : my_login_required開始調用
      • return wrapper(*args, **kwargs)
    7. 執行 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錯誤
或者
沒有由於裝飾器函數沒有的正確返回結果,被裝飾函數未執行


創作不易,如您感覺有收穫,點個讚唄!~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章