裝飾器:本質(zhì)是函數(shù),功能是裝飾其他函數(shù),就是為其他函數(shù)添加附加功能
編寫裝飾器的原則:
- 不能修改被裝飾函數(shù)的源代碼
- 不能修改被裝飾函數(shù)的調(diào)用方式
接下來模擬一個應(yīng)用場景從頭編寫一個裝飾器:
有一個視頻網(wǎng)站有四個模塊:
def home():
print("---首頁----")
def domestic():
print("----國內(nèi)專區(qū)----")
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
視頻網(wǎng)站運(yùn)營一段一時間后積攢了些客戶,現(xiàn)在要為歐美和日韓模塊進(jìn)行收費(fèi),所以,調(diào)用該模塊時,需要判斷是否是付費(fèi)vip會員。
通過之前的函數(shù)基礎(chǔ)知識的學(xué)習(xí),輕易能想到的實(shí)現(xiàn)思路是:
單獨(dú)定義一個登陸函數(shù),在調(diào)用歐美和日韓模塊時調(diào)用:
user_status = False #用戶登錄了就把這個改成True
def login():
_username = "LZ" #假裝這是DB里存的用戶信息
_password = "abc123" #假裝這是DB里存的用戶信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
else:
print("用戶已登錄,驗(yàn)證通過...")
def home():
print("---首頁----")
def domestic():
print("----國內(nèi)專區(qū)----")
def america():
login() #執(zhí)行前加上驗(yàn)證
print("----歐美專區(qū)----")
def japan():
login() #執(zhí)行前加上驗(yàn)證
print("----日韓專區(qū)----")
#調(diào)用模塊
home()
domestic()
america()
japan()
這樣就能完成需求。但是??!這違反了軟件開發(fā)中的一個原則“開放-封閉”原則,簡單來說,它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改,但可以被擴(kuò)展:
開放——封閉
- 封閉:已實(shí)現(xiàn)的功能代碼塊
- 開放:對擴(kuò)展開放
抱著不改變功能模塊源碼的想法我們會想到用高階函數(shù),把模塊函數(shù)當(dāng)參數(shù)傳入login()函數(shù)中:
user_status = False #用戶登錄了就把這個改成True
def login(func): #把要執(zhí)行的模塊從這里傳進(jìn)來
_username = "LZ" #假裝這是DB里存的用戶信息
_password = "abc123" #假裝這是DB里存的用戶信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func() # 看這里看這里,只要驗(yàn)證通過了,就調(diào)用相應(yīng)功能
#原功能模塊不改
#調(diào)用模塊
home()
domestic()
login(america)
login(japan)
這樣,不改變功能模塊源碼也完成了需求,但是改變了調(diào)用方式!試想:每個模塊不同的人負(fù)責(zé),這樣的話就得要求負(fù)責(zé)相應(yīng)模塊的人都得去改調(diào)用方式。這。。。很危險(xiǎn)啊。
現(xiàn)在在分析一下:之前函數(shù)基礎(chǔ)時講過函數(shù)也是變量,不改變函數(shù)調(diào)用方式的話,我們可以將login(america)賦值給america。但是有一個問題是當(dāng)執(zhí)行america = login(america)代碼時,就已經(jīng)把america執(zhí)行了啊!我們接下來要做的就是,當(dāng)america = login(america)代碼時,返回一個函數(shù),但是不執(zhí)行。等再調(diào)用america ()才執(zhí)行:
def login(func): #把要執(zhí)行的模塊從這里傳進(jìn)來
def inner():#再定義一層函數(shù)
_username = "alex" #假裝這是DB里存的用戶信息
_password = "abc!23" #假裝這是DB里存的用戶信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func() # 看這里看這里,只要驗(yàn)證通過了,就調(diào)用相應(yīng)功能
return inner #用戶調(diào)用login時,只會返回inner的內(nèi)存地址,下次再調(diào)用時加上()才會執(zhí)行inner函數(shù)
這樣當(dāng)調(diào)用時先america = login(america)你在這里相當(dāng)于把a(bǔ)merica這個函數(shù)替換了,然后在調(diào)用america ()。Python中提供語法在要被裝飾的函數(shù)前寫@login就相當(dāng)于america = login(america)。所以調(diào)用時:
#調(diào)用模塊
home()
domestic()
@login
america()
@login
japan()
執(zhí)行完america = login(america)之后,america就相當(dāng)于調(diào)用的時inner.所以,如果原來的america如果有參數(shù)的話,在inner函數(shù)上添加參數(shù)即可:
def login(func): #把要執(zhí)行的模塊從這里傳進(jìn)來
def inner(*args):#再定義一層函數(shù)
_username = "alex" #假裝這是DB里存的用戶信息
_password = "abc!23" #假裝這是DB里存的用戶信息
global user_status
if user_status == False:
username = input("user:")
password = input("pasword:")
if username == _username and password == _password:
print("welcome login....")
user_status = True
else:
print("wrong username or password!")
if user_status == True:
func() # 看這里看這里,只要驗(yàn)證通過了,就調(diào)用相應(yīng)功能
return inner #用戶調(diào)用login時,只會返回inner的內(nèi)存地址,下次再調(diào)用時加上()才會執(zhí)行inner函數(shù)
#調(diào)用
@login
america('jack')
寫到這,login就是一個裝飾器了。它是一個函數(shù),只是給原來的函數(shù)增加了驗(yàn)證功能。既沒有改變原函數(shù)代碼,也沒有改變調(diào)用方式!