一、裝飾器基礎
1、什麼是裝飾器?
裝飾器就是一個傳回函數的高階函數。顧名思義,裝飾器就是對現有的函數的裝飾,在不改變現有函數的的源代碼以及調用方式的基礎上添加新的功能。是以,一個裝飾器應該滿足以下三點原則:
- 不改變現有函數的源代碼(開放封閉原則)
- 不改變現有函數的調用方式
- 能夠添加滿足需求的新功能
2、實作基礎
如何實作裝飾器的三條原則呢?需要使用以下三個知識點:
- 高階函數
- 函數嵌套
- 閉包
3、高階函數
高階函數就是變量可以指向函數,并且函數的參數能接收變量,那麼一個函數就可以接受另一個函數作為參數,最後傳回值可以是函數。是以一個高階函數滿足以下兩點之一就是高階函數。
- 函數接收的參數是一個函數名
- .函數的傳回值是一個函數名
如果僅僅通過高階函數能否滿足裝飾器的原則呢?假如現在有一個需求,計算一個函數的的運作時間,一般情況下應該這樣就可以實作的。
import time
def Bar():
start_time=time.time()
time.sleep(2)
print("我是bar函數")
end_time=time.time()
return end_time-start_time
res=Bar()
print(res)
###################輸出結果##########
#我是bar函數
#2.0001144409179688
這樣是可以實作的,但是如果需要為1000或者更多函數添加此項功能呢?在不違背開放封閉的前提下,隻能在原有功能的基礎上進行裝飾Bar函數。
#函數接收的參數是一個函數名
import time
def Bar():
time.sleep(2)
print("我是bar函數")
def CalTime(func):
start_time=time.time()
func()
end_time=time.time()
return end_time-start_time
res=CalTime(Bar)
print(res)
######################輸出#############
#我是bar函數
#2.0001144409179688
這裡應用高階函數的第一點,傳入函數名,可以看到CalTime函數實作了計算時間功能,并且沒有改變Bar函數源代碼,但是函數的調用方式發生了變化,通過:
CalTime(Bar)
進行調用,是以傳入函數名:
- 可以實作功能
- 改變了調用方式
- 源代碼未改變
然後利用高階函數的第二個特性,傳回函數名:
#函數傳回的是一個函數名
import time
def Bar():
time.sleep(2)
print("我是bar函數")
def CalTime(func):
start_time=time.time()
func()
end_time=time.time()
print( end_time-start_time)
return func
Bar=CalTime(Bar)
Bar()
#################輸出#########
#我是bar函數
#2.0001144409179688
#我是bar函數
從上面的輸出看好像實作了裝飾器的原則呀,不,細看後可以發現,調用方式沒有發生變化,但是功能沒有實作,它多列印了一行内容,這是由于傳回函數名的原因,是以:
- 沒有實作功能
- 調用方式未發生變化
此時,可能有的人想将兩個綜合起來不就實作了嗎?事實上結果是:
此時明顯高階函數不能完全解決這個問題,引入嵌套函數?
4、函數嵌套
python中定義函數的關鍵字是def,如果在函數中使用def關鍵字再建立函數,這時就會形成函數嵌套。
def f1():
name="bar"
def f2():
print(name)
f2()
f1() #bar
像上述這樣的形式屬于函數嵌套,而函數中調用其它函數不屬于函數嵌套,那麼引入函數嵌套到底有什麼作用呢?從上述的執行結果可以看到列印出name的值,f2作用域中沒有就會向上一層中尋找變量,這裡就是需要利用這個特性來做文章的。
import time
def Bar():
time.sleep(2)
print("我是bar函數")
def CalTime(func):
def wrapper():
start_time=time.time()
func()
end_time=time.time()
print(end_time-start_time)
return wrapper
Bar=CalTime(Bar)
Bar()
##############輸出####
#我是bar函數
#2.0001144409179688
此時可以看到我在wrapper函數中執行了外層作用域中函數變量func,而且可以看到輸出結果是正确的,這樣裝飾器基本實作了,可能有的人說閉包呢?函數嵌套裡面已經包括閉包了,wrapper函數是一個閉包,CalTime函數也是一個閉包,當然這還不是一個完整的裝飾器,不可能每一次調用方式都需要這樣寫吧?
Bar=CalTime(Bar)
Bar()
是以,此時引入“@”
import time
def CalTime(func):
def wrapper():
start_time=time.time()
func()
end_time=time.time()
print(end_time-start_time)
return wrapper
@CalTime # Bar=CalTime(Bar)
def Bar():
time.sleep(2)
print("我是bar函數")
Bar()
這樣一個簡單的裝飾器就出來了
@CalTime 就相當于 Bar=CalTime(Bar)
可以整理一下運作過程,Bar()運作相當于CalTime(Bar)(),CalTime(Bar)()相當于執行函數wrapper(),而在wrapper函數中又執行了func函數(也就是Bar())。
二、裝飾器
1、裝飾帶參數的函數
上面是簡單的裝飾器,裝飾的函數是沒有參數的,如果Bar()是帶有參數的函數,此時又應該如何解決呢?
import time
def CalTime(func):
def wrapper(*args,**kwargs):#相當于Bar(1,b=2) args=(1,) kwargs={"b":2}
start_time=time.time()
func(*args,**kwargs) #運作Bar() func(*(1,),**{'b':18})
end_time=time.time()
print(end_time-start_time)
return wrapper
@CalTime # Bar=CalTime(Bar)
def Bar(a,b):
time.sleep(2)
print("我是bar函數")
Bar(1,b=2)
#########################輸出################
#我是bar函數
#2.0001144409179688
這就是被修飾的函數加上可變的參數,這樣裝飾器會更加實用
2、裝飾帶傳回值的函數
上面已經說了裝飾帶參數的函數,但是卻沒有傳回值,可以由上面的代碼看到執行wrapper函數後沒有任何傳回值,可以列印Bar函數的執行結果為None。
...
res=Bar(1,b=2)
print(res)#None
...
此時可以加上傳回值:
import time
def CalTime(func):
def wrapper(*args,**kwargs):#相當于Bar(1,b=2) args=(1,) kwargs={"b":2}
start_time=time.time()
res=func(*args,**kwargs) #運作Bar() func(*(1,),**{'b':18}) 使用變量res存放被裝飾函數的傳回
end_time=time.time()
print(end_time-start_time)
return res #包裝函數并且傳回res,實作傳回值的傳遞
return wrapper
@CalTime # Bar=CalTime(Bar)
def Bar(a,b):
time.sleep(2)
return "我是bar函數"
res=Bar(1,b=2)
print(res)#None
#########################輸出################
#2.0001144409179688
#我是bar函數
3、functools.wraps
如何保持原函數的元資訊呢?
從上面的代碼如果列印:
...
res=Bar(1,b=2)
print(res)
print(Bar.__name__) #wrapper
...
可以看到列印Bar函數名字輸出wrapper,這樣并沒有保留原函數的元資訊,此時可以利用functools.wraps達到這個效果:

import time
import functools
def CalTime(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):#相當于Bar(1,b=2) args=(1,) kwargs={"b":2}
start_time=time.time()
res=func(*args,**kwargs) #運作Bar() func(*(1,),**{'b':18}) 使用變量res存放被裝飾函數的傳回
end_time=time.time()
print(end_time-start_time)
return res #包裝函數并且傳回res,實作傳回值的傳遞
return wrapper
@CalTime # Bar=CalTime(Bar)
def Bar(a,b):
time.sleep(2)
return "我是bar函數"
res=Bar(1,b=2)
print(res)
print(Bar.__name__) #Bar
加入functools.wraps
此時一個完整的裝飾器就出來了。
4、裝飾器接收參數
有時針對不同的被裝飾的函數,裝飾器需要做一些調整,可以将裝飾器傳入需要的參數,完成一定的功能。
這是一般的裝飾器:

水果裝飾器
import functools
def fruit(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("顔色:%s,價格:%s"%(args[0],args[1]))
return func(*args,**kwargs)
return wrapper
@fruit
def apple(color,price):
return "我愛吃蘋果"
@fruit
def pear(color,price):
return "我愛吃梨子"
apple("綠色",20)#顔色:綠色,價格:20
pear("黃色",10) #顔色:黃色,價格:10
現在需求是根據不同的愛好選擇不同的水果,也就是傳入不同水果類型,執行不同水果函數:
import functools
def outer(type=None):
def fruit(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("顔色:%s,價格:%s"%(args[0],args[1]))
if type=="apple":
return func(*args,**kwargs)
elif type=="pear":
return func(*args, **kwargs)
else:
print("無可用函數執行!")
return wrapper
return fruit
@outer(type="apple") #@fruit 相當于apple=fruit(apple)
def apple(color,price):
return "我愛吃蘋果"
@outer(type="pear")
def pear(color,price):
return "我愛吃梨子"
res=apple("綠色",20)#顔色:綠色,價格:20
print(res)#我愛吃蘋果
# pear("黃色",10) #顔色:黃色,價格:10
從上面可以看出,這樣又加一層嵌套函數,但實際作用就是傳入一個type值,其它的還是沒變:
@outer(type="apple")----------》@fruit------------》相當于apple=fruit(apple)
這也是對裝飾器的進一步擴充了。
三、執行個體
1、登陸驗證
一般web頁面登陸後需要記錄登入後使用者的資訊,這樣進入其它頁面就不用再次登陸了,這裡模拟一下登陸認證,首先可以寫一個登陸的處理函數,如果登陸成功後,将使用者的的資訊寫入session中:

def login(request):
if request.method=="GET":
return render(request,'login.html')
username=request.POST.get("username")
password=request.POST.get("password")
user=models.UserInfo.objects.filter(username=username,password=password).first()
if user:
request.session['user_info'] = user
return redirect("/index/")
login
寫一個裝飾器,進入首頁後的登陸判斷:
def check_login(func):
def wrapper(request,*args,**kwargs):
if request.session['user_info']:
return func(request,*args,**kwargs)
else:
return redirect("/login/")
return wrapper
再進入其它頁面使用這個裝飾器:
@check_login
def index(request):
return render(request,"index.html")
2、登陸驗證類型的選擇
上面使用的session進行判斷的,當然,還可以給裝飾器加上參數,選擇不同的驗證方式,隻需要做簡單的修改:
def auth_type(type=None):
def check_login(func):
def wrapper(request,*args,**kwargs):
if type == "file": #從檔案中取出使用者名和密碼進行校驗
print("用檔案的方式進行驗證")
if request.POST.get("username") == "xxx" and request.POST.get("passworld") == "yyy":
return func(request, *args, **kwargs)
else:
return redirect("/login/")
else:
if request.session['user_info']:
return func(request,*args,**kwargs)
else:
return redirect("/login/")
return wrapper
return check_login
其它的函數被裝飾:
@auth_type(type="file")
def home(request):
return render(request,"home.html")
@auth_type
def index(request):
return render(request,"index.html")
作者:iveBoy
出處:http://www.cnblogs.com/shenjianping/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連接配接,否則保留追究法律責任的權利。