目錄
裝飾器概念
裝飾器本質就是函數,功能是爲其他函數添加附加功能。裝飾器的原則如下
- 不修改被裝飾函數的源代碼
- 不修改被裝飾函數的調用方式
裝飾器的實現
要實現裝飾器,就是遵循裝飾器的原則來實現裝飾器,具體做法就是 裝飾器=高階函數+函數嵌套/閉包。
我們用一個例子來實現以下功能。有個函數foo,執行時間較長,我們在不修改foo函數的源代碼、不修改foo函數的調用方式的前提下,顯示foo函數執行的時間。這個需求就用裝飾器來實現,我們一點一點接近裝飾器的完整實現。
import time
def foo():
"""
模擬foo函數運行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
# foo函數的調用方式就是直接調用
foo()
修改被裝飾函數的代碼(非裝飾器實現)
我們可以選擇在foo函數開始執行的時候記下時間,在foo結束執行的時候再記下時間,從而得到foo函數的執行時間。但是這樣就必須修改foo函數的源代碼,這在大型項目裏面很可能會引發連鎖反應,導致其他調用的地方出錯。
import time
def foo():
"""
模擬foo函數運行了3秒左右
:return:
"""
start_time = time.time()
time.sleep(3)
print('from foo')
stop_time = time.time()
print("foo執行時間 %s" % (stop_time - start_time))
pass
# foo函數的調用方式就是直接調用
foo()
# from foo
# foo執行時間 3.0006065368652344
修改被裝飾函數的調用方式(非裝飾器實現)
我們還可以保持foo函數不修改,將foo作爲參數傳給裝飾器函數timer,由timer來記錄foo的執行時間。這個樣保證了foo代碼不變,但是foo函數的調用方式發生了變化。
import time
def timer(func):
start_time = time.time()
func()
stop_time = time.time()
print("running time: %s" % (stop_time - start_time))
return func
def foo():
"""
模擬foo函數運行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
timer(foo)
# from foo
# running time: 3.013805627822876
裝飾器的實現(不完整的實現level1)
有沒有既不修改被裝飾函數的源代碼、又不修改被裝飾函數的調用方式,這樣兩全其美的實現呢?那就是裝飾器。裝飾器函數:將被裝飾函數作爲入參,這就需要定義高階函數;將附加了裝飾功能的函數作爲返回值返回,同時保證被裝飾函數在裝飾器裏面不被調用,這就需要函數嵌套/閉包。最後,要保證調用方式不變,那麼就將返回值賦給被裝飾函數。
具體到被裝飾函數foo和裝飾器timer的例子。就是foo要作爲timer的入參,timer裏面定義嵌套函數wrapper,wrapper被調用時foo和修飾功能都被執行,將wrapper作爲返回值返回,返回值付給foo。
import time
def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("運行時間 %s" % (stop_time - start_time))
pass
return wrapper
def foo():
"""
模擬foo函數運行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
foo = timer(foo)
foo()
# from foo
# 運行時間 3.012805223464966
裝飾器實現的語法糖(不完整的實現level2)
上面的實現還有一個問題,那麼就是每次都要經過裝飾器返回值付給被修飾函數的過程。這個Python在語法成名予以解決。那就是把 @裝飾器 放在被裝飾函數定義的前一行,這樣就裝飾了。
import time
def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print("運行時間 %s" % (stop_time - start_time))
pass
return wrapper
@timer
def foo():
"""
模擬foo函數運行了3秒左右
:return:
"""
time.sleep(3)
print('from foo')
pass
foo()
# from foo
# 運行時間 3.012805461883545
函數閉包加上返回值和參數(基本完整的裝飾器實現)
上面實現的裝飾器還不夠完整。如果被裝飾的函數還有入參和返回值,那麼上面的實現方式是不行的。我們來解決這個問題。
首先,如果被裝飾函數有入參,在執行裝飾器返回的嵌套函數裏面也要有相應的入參。爲了保證裝飾器適應性任意多個入參,這個嵌套函數應該有邊長的參數,因此wrapper的入參是*args,**kwargs。調用func的時候也應當相應的加參數*args,**kwargs。注意,這兩個*args,**kwargs的含義是不一樣的。wrapper的參數是函數定義,回憶函數基礎那篇博客變長參數的部分,這裏的args和kwargs分別是元組和字典。func調用的地方是函數調用的入參,是解壓序列,將args元組解壓序列成位置參數,將kwargs解壓序列成關鍵字參數。因此,在裝飾器實現的時候,都應保證wrapper和func的參數是*args,**kwargs。
其次,如果被裝飾函數有返回值,在執行裝飾器返回的嵌套函數裏面也要有相應的返回值。解決辦法就是將返回值記錄下來。在適當的地方返回。
import time
def timer(func):
def wrapper(*args, **kwargs): # 這裏的*args, **kwargs是函數定義,表示可邊長參數
start_time = time.time()
res = func(*args, **kwargs) # 這裏的*args, **kwargs是函數調用的入參,將wrapper的參數解壓序列
stop_time = time.time()
print("運行時間 %s" % (stop_time - start_time))
return res
pass
return wrapper
@timer
def foo(os, db, app):
"""
模擬foo函數運行了3秒左右
:return:
"""
time.sleep(0.2)
res = 'os->%s,db->%s,app->%s' % (os, db, app)
print('from foo')
return res
pass
RES = foo('linux', db='mysql', app='apache')
print(RES)
# from foo
# 運行時間 0.20280051231384277
# os->linux,db->mysql,app->apache
過渡內容
這一部分爲後面做鋪墊,例子完全來自我所看視頻教程的內容。需求是,在首頁index、個人中心home和購物車shopping_car執行前進行身份驗證,不能修改這三個函數的調用方式。
def index():
print('welcome to front page')
pass
def home(name):
print('welcome home%s' % name)
pass
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# welcome to front page
# welcome homekevin
# kevin's shopping car has [food,drink,toy]
用全局變量來模擬數據庫記錄和當前用戶session。代碼如下。
# 用戶列表,模擬數據庫存儲用戶信息
user_dic = [
{'username': 'alice', 'password': '123'},
{'username': 'kevin', 'password': '123'},
{'username': 'bob', 'password': '123'},
{'username': 'charlie', 'password': '123'},
]
# 當前用戶,模擬session
curr_user = {'username': None, 'login': False}
def auth_func(func):
def auth_check(*args, **kwargs):
# 如果有session,直接執行功能
if curr_user['username'] and curr_user['login']:
res = func(*args, **kwargs)
return res
# 否則驗證身份
username = input('username:').strip()
password = input('password:').strip()
for user in user_dic:
if username == user['username'] and password == user['password']:
# 身份驗證成功,更新session,並執行功能
curr_user['username'] = username
curr_user['login'] = True
res = func(*args, **kwargs)
return res
pass
# 驗證不成功,報錯
print('wrong username or password!')
pass
return auth_check
@auth_func
def index():
print('welcome to front page')
pass
@auth_func
def home(name):
print('welcome home, %s' % name)
pass
@auth_func
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# welcome home, kevin
# kevin's shopping car has [food,drink,toy]
帶參數的裝飾器
接上面過渡內容,如果我們的驗證方式有不同,index用數據庫,home和shopping_car用其他的驗證方式,那怎麼辦呢?可以用帶有參數的裝飾器。我們在上面auth_func外再包一層auth,再調用裝飾器的時候,調用auth執行的結果。代碼如下。
# 用戶列表,模擬數據庫存儲用戶信息
user_dic = [
{'username': 'alice', 'password': '123'},
{'username': 'kevin', 'password': '123'},
{'username': 'bob', 'password': '123'},
{'username': 'charlie', 'password': '123'},
]
# 當前用戶,模擬session
curr_user = {'username': None, 'login': False}
def auth(auth_type='filedb'):
def auth_func(func):
def auth_check(*args, **kwargs):
# 這裏通過auth_type可以做更多的選擇
if auth_type == 'filedb':
# 如果有session,直接執行功能
if curr_user['username'] and curr_user['login']:
res = func(*args, **kwargs)
return res
# 否則驗證身份
username = input('username:').strip()
password = input('password:').strip()
for user in user_dic:
if username == user['username'] and password == user['password']:
# 身份驗證成功,更新session,並執行功能
curr_user['username'] = username
curr_user['login'] = True
res = func(*args, **kwargs)
return res
pass
# 驗證不成功,報錯
print('wrong username or password!')
elif auth_type == 'ldap':
print('ldap check')
res = func(*args, **kwargs)
return res
else:
print('what the fuck!')
pass
return auth_check
# 將auth_func返回
return auth_func
# @ auth調用的結果
# 也就相當於@auth_func
@auth()
def index():
print('welcome to front page')
pass
# @ auth調用的結果
# 也就相當於@auth_func
@auth(auth_type='ldap')
def home(name):
print('welcome home, %s' % name)
pass
# @ auth調用的結果
# 也就相當於@auth_func
@auth(auth_type='kerbers')
def shopping_car(name):
print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))
pass
index()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# ldap check
# welcome home, kevin
# what the fuck!