一文讓你徹底明白Python裝飾器原理,從此面試工作再也不怕了。轉載請注明出處http://www.cnblogs.com/Wxtrkbc/p/5486253.html
一、裝飾器
裝飾器可以使函數執行前和執行后分別執行其他的附加功能,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator),裝飾器的功能非常強大,但是理解起來有些困難,因此我盡量用最簡單的例子一步步的說明這個原理。
1、不帶參數的裝飾器
假設我定義了一個函數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
2、包含任意參數的裝飾器
在實際中,我們的裝飾器可能應用到不同的函數中去,這些函數的參數都不一樣,那么我們怎么實現一個對任意參數都能實現功能的裝飾器?還記得我寫函數那篇博客中,就寫一種可以接受任意參數的函數,下面來看看如何將其應用到裝飾器中去
#其實只要將上面一種不帶參數的裝飾器修改一下就可以了
#修改也很簡單,只需將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
3、使用兩個裝飾器
當一個裝飾器不夠用的話,我們就可以用兩個裝飾器,當然理解起來也就更復雜了,當使用兩個裝飾器的話,首先將函數與內層裝飾器結合然后在與外層裝飾器相結合,要理解使用@語法的時候到底執行了什么,是理解裝飾器的關鍵。這里還是用最簡單的例子來進行說明。
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('結束')
4、帶參數的裝飾器
前面的裝飾器本身沒有帶參數,如果要寫一個帶參數的裝飾器怎么辦,那么我們就需要寫一個三層的裝飾器,而且前面寫的裝飾器都不太規范,下面來寫一個比較規范帶參數的裝飾器,下面來看一下代碼,大家可以將下面的代碼自我運行一下
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)
