目錄
1、函數裝飾器
1.1、閉包函數
1.2、裝飾器語法
1.3、裝飾帶參數的函數
1.4、被裝飾函數的身份問題
1.4.1、解決被裝飾函數的身份問題
1.5、裝飾器本身攜帶/傳參數
1.6、嵌套多個裝飾器
2、類裝飾器
裝飾器顧名思義作為一個裝飾的作用,本身不改變被裝飾對象的原本功能,只是對被裝飾對象擴展了額外的功能,裝飾器分為兩類:函數裝飾器和類裝飾器
1、函數裝飾器
說明:函數裝飾器是一個函數接受另一個函數作為參數的函數,它包裝了函數的行為,并返回包裝后的函數。所以裝飾器的目的是為了擴展函數的功能,而不是修改函數本身,裝飾器器的本質就是閉包函數。下面說明一下什么是閉包函數:
1.1、閉包函數
閉包的定義:在函數嵌套的前提下,內部函數使用外部函數的變量,并且外部函數返回內部函數
我們把這個使用外部函數變量的內部函數稱為閉包。
閉包函數的作用:變量在函數運行之后銷毀,但有的時候需要這個函數的變量,這時候使用閉包函數解決。
閉包函數的三要素:
- 1.實現函數嵌套
- 2.內部函數使用外部函數的變量
- 3.外部函數返回內部函數
我們來看一個示例:
def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapperdef hello():print("hello world")# 閉包函數的使用
hello_new = wrapper_hello(hello)
# 函數的調用
hello_new()
運行結果:
解釋:
上面的代碼中定義了兩個函數,其中hello()函數是一個普通函數,它的功能是打印hello world。
我們來看看wrapper_hello()函數具體實現了什么功能:
- 1.首先它的參數func是一個可調用對象。
- 2.然后它的內部定義了一個函數wrapper(),并把wrapper對象作為返回值。
- 3.wrapper()函數內部執行過程:
- 先打印輸出了“this is start”
- 然后執行func()
- 最后打印輸出"this is end"
所以我們可以說wrapper_hello()函數擴展了hello()函數的功能,hello()原本實現的功能并沒有改變。
1.2、裝飾器語法
說明:Python提供了一種語法來定義裝飾器。稱為糖語法(通過@修飾目標函數), 它可以將修飾后函數賦值給修飾函數本身,所以調用函數時,還是直接調用,裝飾器只是給函數增加額外的功能,本身并不改變函數功能和調用執行方式。
示例:
def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello():print("hello world")# 還是正常的調用函數
hello()# 調用等價于
# hello = wrapper_hello(hello)
# hello()
1.3、裝飾帶參數的函數
說明:因為Python不允許裝飾器接受被裝飾對象的參數,所以要想實現裝飾帶參數的函數,在裝飾器內部函數中使用 *args 和 **kwargs 來實現。
示例:
def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return resreturn wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 還是正常的調用函數
hello("Tom")
注意:要想獲取目標函數的返回值結果,必須要在裝飾器內部返回執行結果,否則無法獲取執行的結果,原因是func函數執行完之后就會被銷毀,所以需要在裝飾器內部保存目標函數的執行結果。
1.4、被裝飾函數的身份問題
說明:如果查看修飾后函數的名字,或者使用內置的help函數查看,發現被修飾函數的名字是wrapper。因為Python認為現在的函數是裝飾器函數的內部函數。
示例:
def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return resreturn wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函數的名字
print(hello.__name__)
help(hello)
運行結果:
1.4.1、解決被裝飾函數的身份問題
說明:可以使用functools.wraps裝飾器解決這個問題,它的作用是保留原函數的信息。 這可以幫助我們在運行時獲取原本對象的信息,比如函數的名字,參數等。
注:也可以顯示使用wrapper.__name__ = func.__name__的方法實現
示例:
def wrapper_hello(func: callable):@functools.wraps(func)def wrapper(*args, **kwargs):print("this is start")# 注意如果func函數有返回值,需要使用一個對象來接受返回值,不然func執行完成后就銷毀了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun執行結果:", res)print("this is end")return res# 等價于@functools.wraps(func)的作用# wrapper.__name__ = func.__name__return wrapper# 裝飾器糖語法寫法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函數的名字
print(hello.__name__)
help(hello)
運行結果:
1.5、裝飾器本身攜帶/傳參數
說明:為了更好地理解裝飾器參數的必要性, 我們實現一個repeat裝飾器,它接受一個數字作為輸入。這個裝飾器的功能是:重復執行目標函數給定的次數。
示例1:
import functools
def repeat(num_times):def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat@repeat(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')
示例2:
import functools
def repeat1(num_times):def repeat():def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat# 注意這里返回的內部repeat函數的調用return repeat()@repeat1(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')
執行結果都為以下結果:
解釋:
裝飾器執行原理:不管裝飾器嵌套了多少層函數,執行順序是從最外層的函數開始執行也就是repeat1函數,原因是理解為隊列,遵循先進先出的原理,所以從最外層的函數先執行。
所以若要裝飾器可以傳參數最多只需要嵌套3層即可,再嵌套就顯得多余和沒有必要。
注意:
- 1、裝飾器函數多層嵌套,需要每層都要有返回值,即每層返回對應的函數名。
- 2、函數括號的參數屬于整個函數內部的“全局變量”,也就是不管函數內部嵌套了多少層函數,都可以使用這些變量
1.6、嵌套多個裝飾器
說明:通過堆疊的方式將多個裝飾器應用到一個函數上。 這些裝飾器按照順序從上到下開始執行
示例:
import functools
# 裝飾器嵌套
def start_end(func):@functools.wraps(func)def wrapper(*args, **kwargs):print('this is start')result = func(*args, **kwargs)print('this is end')return resultreturn wrapperdef debug(func):@functools.wraps(func)def wrapper(*args, **kwargs):args_repr = [repr(a) for a in args]kwargs_repr = [f"{k} = {v!r}" for k, v in kwargs.items()]signature = ", ".join(args_repr + kwargs_repr)# print(signature)print(f"calling {func.__name__} ({signature})")result = func(*args, **kwargs)print(f" {func.__name__!r} returned {result!r}")return resultreturn wrapperdef hello(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("這里是hello函數開始")result = func(*args, **kwargs)print("這里是hello函數結束")return wrapper@start_end
@debug
@hello
def say_hello(name):res = f'hello {name}'print(res)return ressay_hello("張三")
運行結果:
解釋:
從運行結果可以看出,多重裝飾器的嵌套是從上至下開始執行的,但是并不是等一個裝飾器執行完了再執行下一個,而是從第一個裝飾器開始執行到目標函數停止繼續尋找下一個裝飾器執行,然后執行第二個裝飾器同樣執行到目標函數停止繼續尋找下一個裝飾器執行,按照該方式繼續執行,直到執行到最后一個裝飾器,才開始執行目標函數,然后層層返回到最外層。
執行原理:就是遵循的棧的先進后出原理。
注意:目標函數不管嵌套了多少層裝飾器,目標函數有且僅執行一次。
2、類裝飾器
說明:我們也可以使用類作為裝飾器。 但是必須實現__call__()方法,目的是使我們的對象可調用。 類裝飾器通常用于維護狀態。
示例:我們記錄函數被調用的次數。 __call__本質上和wrapper()方法是一樣的。 它添加了一些功能,執行函數,并返回其結果。
注意:這里我們使用functools.update_wrapper()而不是functools.wraps()來保留我們的函數的信息。
示例:
import functoolsclass CountCallNums:def __init__(self, func):functools.update_wrapper(self,func)self.func = funcself.count = 0def __call__(self, *args, **kwargs):self.count += 1print(f"函數{self.func.__name__} 被執行了 {self.count}次")return self.func(*args, **kwargs)@CountCallNums
def hello():print("hello")hello()
hello()# 上面裝飾器等價于
# hello = CountCallNums(hello)
# hello()
# hello()print(hello.__name__)
help(hello)
運行結果: