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