什麼是裝飾器?
在說裝飾器之前啊. 我們先說一個軟體設計的原則: 開閉原則, 又被成為開放封閉原則,你的代碼對功能的擴充是開放的你的程式對修改源代碼是封閉的. 這樣的軟體設計思路可以更好的維護和開發。
開放:對功能擴充開放
封閉:對修改代碼封閉
談裝飾器前,還要先要明白一件事,Python 中的函數可以像普通變量一樣當做參數傳遞給另外一個函數,例如:
def foo():
print("foo")
def bar(func):
func()
bar(foo)
裝飾器的目的: 在不改變原來代碼的基礎上. 給代碼添加新功能。
裝飾器形成過程
簡單裝飾器
def func1():
print("歡迎通路首頁")
# 添加一個登入驗證的功能
def wrapper(fn):
def inner():
print("請先登入....")
fn()
return inner
func1 = wrapper(func1)
func1()
# 裝飾器的基本雛形
# def wrapper(fn): # fn:目标函數.
# def inner():
# '''執行函數之前'''
# fn() # 執行被裝飾的函數
# '''執行函數之後'''
# return inner
來看一下,執行流程:

文法糖
但是如果有多個函數,都需要添加登入驗證的功能,每次都得func1 = timer(func1)?這樣還是有點麻煩,因為這些函數的函數名可能是不相同,有func1,func2,graph,等等,是以更簡單的方法,python給你提供了,那就是文法糖。 @裝飾器
def wrapper(fn):
def inner():
print("請先登入....")
fn()
return inner
@wrapper # 相當于 => func1 = wrapper(func1)
def func1():
print("歡迎通路首頁")
func1()
有個小問題,就是檢視func1函數名和函數注釋都變成inner了。
print(func1.__doc__) # 檢視函數注釋資訊
print(func1.__name__) # 檢視函數名
# 我是内部函數
# inner
如何解決???
from functools import wraps
def wrapper(fn):
@wraps(fn) # 改變inner的名字
def inner():
"""我是内部函數"""
print("請先登入....")
fn()
return inner
@wrapper # 相當于 => func1 = wrapper(func1)
def func1():
""" 首頁函數 """
print("歡迎通路首頁")
func1()
print(func1.__doc__)
print(func1.__name__)
# 首頁函數
# func1
被裝飾函數帶參數和傳回值
from functools import wraps
def wrapper(fn): # fn: 目标函數, 被裝飾的函數
@wraps(fn) # 改變inner的名字
def inner(*args,**kwargs): # 萬能參數 *args, **kwargs: 接收參數
"""我是内部函數"""
print("請先登入....")
ret = fn(*args,**kwargs) # 調用目标函數.
"""被裝飾函數執行之後"""
return ret # 傳回結果
return inner
@wrapper # 相當于 => func1 = wrapper(func1)
def func1(usernamee):
""" 首頁函數 """
print("歡迎 %s 通路首頁" % usernamee)
return True
@wrapper # 相當于 => func2 = wrapper(func2)
def func2(name,age):
print("名字:%s 年齡:%s" %(name,age))
return True
ret = func1("lishichao") # 實際執行的是inner 參數傳給了inner函數
print(ret)
func2("lishichao","19")
帶有參數的裝飾器
給裝飾器傳參,可以控制裝飾器是否使用。
def wrapper(flag): # 裝飾器參數
def inner1(fn): # fn: 目标函數, 被裝飾的函數
def inner2(*args,**kwargs): # 萬能參數 *args, **kwargs: 接收參數
"""我是内部函數"""
if flag: # 判斷傳入的參數 True就執行裝飾器 False不執行裝飾器
print("請先登入....")
ret = fn(*args,**kwargs) # 調用目标函數.
""""""
return ret # 傳回結果
return inner2
return inner1
@wrapper(False) # 執行流程: 先執行wrapper(False) 傳回裝飾器 再和 @ 拼接起來 @inner1
def func1(usernamee):
""" 首頁函數 """
print("歡迎 %s 通路首頁" % usernamee)
return True
ret = func1("lishichao") # 實際執行的是inner 參數傳給了inner函數
print(ret)
執行流程:
多個裝飾器裝飾一個函數
def wrapper1(fn):
def inner(*args, **kwargs):
print("第一個裝飾器開始")
ret = fn(*args, **kwargs)
print("第一個裝飾器結束")
return ret
return inner
def wrapper2(fn):
def inner(*args, **kwargs):
print("第二個裝飾器開始")
ret = fn(*args, **kwargs)
print("第二個裝飾器結束")
return ret
return inner
def wrapper3(fn):
def inner(*args, **kwargs):
print("第三個裝飾器開始")
ret = fn(*args, **kwargs)
print("第三個裝飾器結束")
return ret
return inner
@wrapper1
@wrapper2
@wrapper3
def func():
print("瞬間來三")
func()
# 執行結果
# 第一個裝飾器開始
# 第二個裝飾器開始
# 第三個裝飾器開始
# 瞬間來三
# 第三個裝飾器結束
# 第二個裝飾器結束
# 第一個裝飾器結束
執行流程:
wrapper1
wrapper2
wrapper3 # 調用函數執行之前
func
wrapper3 # 調用函數執行之後
wrapper2
wrapper1
練習題
1、裝飾器的固定格式
from functools import wraps
def wrapper(flag): # 接收裝飾器的參數
def inner1(fn): # 接收的是被裝飾函數的函數名
@wraps(fn) # 改變inner2函數的名字為被裝飾函數的名字
def inner2(*args,**kwargs): # 無敵傳參,接收參數
if flag: # 判斷是否執行裝飾器
print("被裝飾函數執行之前")
ret = fn(*args,**kwargs) #被裝飾函數, 調用參數
print("被裝飾函數執行之後")
return ret # 傳回值
return inner2
return inner1
@wrapper(True)
def func1():
print("我是被裝飾函數")
return True
func1()
print(func1.__name__)
裝飾器的固定格式
2、編寫裝飾器,在每次執行被裝飾函數之前列印一句’每次執行被裝飾函數之前都得先經過這裡,這裡根據需求添加代碼’
def wrapper(fn):
def inner(*args,**kwargs):
print("每次執行被裝飾函數之前都得先經過這裡,這裡根據需求添加代碼")
ret = fn(*args,**kwargs) #調用被裝飾函數,ret接收被裝飾函數的傳回值
return ret
return inner
@wrapper
def func():
print("我是被裝飾函數")
return "被裝飾函數的傳回值"
ret = func()
print(ret)
View Code
3、編寫裝飾器,在每次執行被裝飾函數之後列印一句’每次執行完被裝飾函數之後都得先經過這裡,這裡根據需求添加代碼’
def wrapper(fn):
def inner(*args,**kwargs):
ret = fn(*args,**kwargs)
print("每次執行完被裝飾函數之後都得先經過這裡,這裡根據需求添加代碼")
return ret
return inner
@wrapper
def func():
print("我是被裝飾函數")
func()
4、編寫裝飾器,在每次執行被裝飾函數之前讓使用者輸入使用者名,密碼,給使用者三次機會,登入成功之後,才能通路該函數.
flag = False
def login():
num = 1
while num <= 3:
username = input("請輸入使用者名:").strip().lower()
password = input("請輸入密碼:").strip().lower()
if username == "lishichao" and password == "123":
global flag
flag = True
break
else:
num += 1
print("輸入錯誤,請重新輸入")
continue
else:
print("輸入超過三次,已鎖定")
def login_wrapper(fn):
def inner():
login()
if flag:
fn()
else:
print("登入失敗")
return inner
@login_wrapper
def func1():
print("歡迎通路文章頁面")
5、編寫裝飾器,為多個函數加上認證的功能(使用者的賬号密碼來源于檔案,隻支援單使用者的賬号密碼,給使用者三次機會)要求登入成功一次,後續的函數都無需再輸入使用者名和密碼
import sys
flag = False
def sign_up():
with open("user_info",mode="w",encoding="utf-8") as f:
username = input("請輸入新增賬號:").strip()
password = input("請輸入注冊密碼:").strip()
f.write("%s|%s\n" %(username,password))
print("注冊成功,請登入")
def login():
num = 1
while num <= 3:
username = input("請輸入賬号:").strip()
if username == "":
print("賬号不能為空")
continue
password = input("請輸入密碼:").strip("|")
if password == "":
print("密碼不能為空,請重新輸入")
continue
num += 1
with open("user_info", mode="r", encoding="utf-8") as f:
for i in f:
user_list = i.strip().split("|")
if username == user_list[0] and password == user_list[1]:
print("登入成功")
global flag
flag = True
return
else:
print("登入失敗,賬号或密碼不正确.")
else:
print("===========================================")
print("登入超過三次,賬号已鎖定")
print("===========================================")
sys.exit()
def wrapper(fn):
def inner(*args,**kwargs):
if flag:
ret = fn(*args,**kwargs)
return ret
else:
login()
return inner
@wrapper
def func1():
print("歡迎通路文章頁面")
@wrapper
def func2():
print("歡迎通路管理頁面")
@wrapper
def func3():
print("歡迎通路評論頁面")
dic = {
"1":sign_up,
"2":login,
"3":func1,
"4":func2,
"5":func3
}
def main():
while 1:
print("""
請選擇:
1.注冊
2.登入
3.通路文章頁面
4.通路管理頁面
5.通路評論頁面
[q] 退出
""")
n = input(">>>").strip().lower()
if n == "q":
break
else:
dic[n]()
main()
6、編寫裝飾器,為多個函數加上認證的功能(使用者的賬号密碼來源于檔案,可支援多賬号密碼)要求登入成功一次(給三次機會),後續的函數都無需再輸入使用者名和密碼。
import sys
flag = False
def sign_up():
with open("user_info",mode="a",encoding="utf-8") as f:
username = input("請輸入新增賬號:").strip()
password = input("請輸入注冊密碼:").strip()
f.write("%s|%s\n" %(username,password))
print("注冊成功,請登入")
def login():
num = 1
while num <= 3:
username = input("請輸入賬号:").strip()
if username == "":
print("賬号不能為空")
continue
password = input("請輸入密碼:").strip("|")
if password == "":
print("密碼不能為空,請重新輸入")
continue
num += 1
with open("user_info", mode="r", encoding="utf-8") as f:
for i in f:
user_list = i.strip().split("|")
if username == user_list[0] and password == user_list[1]:
print("登入成功")
global flag
flag = True
return
else:
print("登入失敗,賬号或密碼不正确.")
else:
print("===========================================")
print("登入超過三次,賬号已鎖定")
print("===========================================")
sys.exit()
def wrapper(fn):
def inner(*args,**kwargs):
if flag:
ret = fn(*args,**kwargs)
return ret
else:
login()
return inner
@wrapper
def func1():
print("歡迎通路文章頁面")
@wrapper
def func2():
print("歡迎通路管理頁面")
@wrapper
def func3():
print("歡迎通路評論頁面")
dic = {
"1":sign_up,
"2":login,
"3":func1,
"4":func2,
"5":func3
}
def main():
while 1:
print("""
請選擇:
1.注冊
2.登入
3.通路文章頁面
4.通路管理頁面
5.通路評論頁面
[q] 退出
""")
n = input(">>>").strip().lower()
if n == "q":
break
else:
dic[n]()
main()
7、給每個函數寫一個記錄日志的功能
功能要求:每一次調用函數之前,要将函數名稱,時間節點記錄到log的日志中。
所需子產品:
import time
struct_time = time.localtime()
print(time.strftime("%Y‐%m‐%d %H:%M:%S",struct_time))
def wrapper_log(flag):
def wrapper(fn):
def innrt(*args,**kwargs):
if flag:
import time
struct_time = time.localtime()
Time = time.strftime("%Y-%m-%d %H:%M:%S",struct_time)
func_name = fn.__name__
with open("func_accecc.log",mode="a",encoding="utf-8") as f:
f.write("在 %s 執行函數: %s\n" %(Time,func_name))
ret = fn(*args,**kwargs)
return ret
return innrt
return wrapper
@wrapper_log(True)
def func1():
print("歡迎通路文章頁面")
@wrapper_log(True)
def func2():
print("歡迎通路評論頁面")
@wrapper_log(True)
def func3():
print("歡迎通路管理頁面")
func1()
func2()
func3()