一文讓你徹底明白Python裝飾器原理,從此面試工作再也不怕了。
裝飾器可以使函數執行前和執行後分別執行其他的附加功能,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator),裝飾器的功能非常強大,但是理解起來有些困難,因此我盡量用最簡單的例子一步步的說明這個原理。
假設我定義了一個函數f,想要在不改變原來函數定義的情況下,在函數運行前打印出start,函數運行後打印出end,要實現這樣一個功能該怎麼實現?看下面如何用一個簡單的裝飾器來實現:
# 使用@語法放在函數的定義上面 相當於執行 f=outer(f),此時f賦值成為了一個新的outer函數, # 此時f函數就指向了outer函數的返回值inner,inner是一個函數名,定義在oute函數裡面 # 原來的f是函數名可簡單理解為一個變量,作為outer函數的參數傳遞進去了 此時參數func相當於f def outer(func): # 定義一個outer函數作為裝飾器 def inner(): # 如果執行inner()函數的話步驟如下: print('start') # 1、首先打印了字符‘start’, r=func() # 2、執行func函數,func函數相當於def f(): print('中') print('end') # 3、接著函數打印‘end’ return r # 4、將func函數的結果返回 return inner @outer def f(): # f=outer(f)=innner print('中') f() # f()相當於inner(),執行inner函數的步驟看上面定義處的注釋
#打印結果順序為 start 中 end
在實際中,我們的裝飾器可能應用到不同的函數中去,這些函數的參數都不一樣,那麼我們怎麼實現一個對任意參數都能實現功能的裝飾器?還記得我寫函數那篇博客中,就寫一種可以接受任意參數的函數,下面來看看如何將其應用到裝飾器中去
#其實只要將上面一種不帶參數的裝飾器修改一下就可以了 #修改也很簡單,只需將inner和func的參數改為 (*args,**kwargs) #其他實現的過程和上面一種一樣,就不再介紹了 def outer(func): def inner(*args,**kwargs): print('start') r=func(*args,**kwargs) # 這裡func(*args,**kwargs)相當於f(a,b) print('end') return r return inner @outer def f(a,b): print(a+b) f(1,4) # f(1,4)相當於inner(1,4) 這裡打印的結果為 start 5 end
當一個裝飾器不夠用的話,我們就可以用兩個裝飾器,當然理解起來也就更復雜了,當使用兩個裝飾器的話,首先將函數與內層裝飾器結合然後在與外層裝飾器相結合,要理解使用@語法的時候到底執行了什麼,是理解裝飾器的關鍵。這裡還是用最簡單的例子來進行說明。
def outer2(func2): def inner2(*args,**kwargs): print('開始') r=func2(*args,**kwargs) print('結束') return r return inner2 def outer1(func1): def inner1(*args,**kwargs): print('start') r=func1(*args,**kwargs) print('end') return r return inner1 @outer2 # 這裡相當於執行了 f=outer1(f) f=outer2(f),步驟如下 @outer1 #1、f=outer1(f) f被重新賦值為outer1(1)的返回值inner1, def f(): # 此時func1為 f():print('f 函數') print('f 函數') #2、f=outer2(f) 類似f=outer2(inner1) f被重新賦值為outer2的返回值inner2 # 此時func2 為inner1函數 inner1裡面func1函數為原來的 f():print('f 函數') f() # 相當於執行 outer2(inner1)() >>開始 # 在outer函數裡面執行,首先打印 ‘開始 ’ >>start # 執行func2 即執行inner1函數 打印 ‘start’ >>f 函數 # 在inner1函數裡面執行 func1 即f()函數,打印 ‘f 函數’ >>end # f函數執行完,接著執行inner1函數裡面的 print('end') >>結束 # 最後執行inner2函數裡面的 print('結束')
前面的裝飾器本身沒有帶參數,如果要寫一個帶參數的裝飾器怎麼辦,那麼我們就需要寫一個三層的裝飾器,而且前面寫的裝飾器都不太規范,下面來寫一個比較規范帶參數的裝飾器,下面來看一下代碼,大家可以將下面的代碼自我運行一下
import functools def log(k=''): #這裡參數定義的是一個默認參數,如果沒有傳入參數,默認為空,可以換成其他類型的參數 def decorator(func): @functools.wraps(func) #這一句的功能是使被裝飾器裝飾的函數的函數名不被改變, def wrapper(*args, **kwargs): print('start') print('{}:{}'.format(k, func.__name__)) #這裡使用了裝飾器的參數k r = func(*args, **kwargs) print('end') return r return wrapper return decorator @log() # fun1=log()(fun1) 裝飾器沒有使用參數 def fun1(a): print(a + 10) fun1(10) # print(fun1.__name__) # 上面裝飾器如果沒有@functools.wraps(func)一句的話,這裡打印出的函數名為wrapper @log('excute') # fun2=log('excute')(fun2) 裝飾器使用給定參數 def fun2(a): print(a + 20) fun2(10)