本質就是一個python函數,它能夠在不改變函數任何代碼的前提下,增長函數額外的功能python
例如:記錄日誌,性能測試,用戶登錄等git
裝飾器的返回值也是一個函數對象。微信
測試該函數執行時間:app
import time def func1(): print('in func1') def timer(func): def inner(): start_time = time.time() time.sleep(0.1) func() end_time = time.time() print('效率: %s' % (end_time - start_time)) return inner func1 = timer(func1) func1()
import time def timer(func): #1 timer(func1),調用該函數 def inner(): #3 start = time.time() #6 func() #7 print(time.time() - start) #9 return inner #4 @timer #2 func1 = timer(func1) def func1(): print('in func1') #8 func1() #5 # 這一個函數自上而下,先找哪裏調用了它,先來到@timer,執行timer(func1),來到inner函數這裏, # 但沒有調用它的方法(inner()),因此跳過這個函數直接到return inner(注意這裏return的是inner, # 這個很關鍵,父級返回子級,可是沒有調用),接下來再從@timer如下去找哪裏還有調用函數,找到func1(), # 如今func1()實際上是timer(func1)(),(不懂看下面這個小的例子,本身試試),外層函數執行完返回inner, # 如今就是調用inner函數了,即timer(func1)() = inner().inner函數體內又包含一個函數的調用(func()), # 此時的func()因爲timer(func1)這裏形參賦值變成了func1(),在#7 這裏對函數進行了調用,執行函數func1, # 打印#8,最後打印#9。 """ def fun(): a = 41 def fun1(): print(a, '啦啦啦') return fun1 fun()() """
import time def timer(func): def inner(*args, **kwargs): start = time.time() re = func(*args, **kwargs) end = time.time() print(end - start) return re # 我擦,又返回了這個func(*args, **kawrgs) return inner @timer # ==> jjj = timer(jjj) def jjj(a): print('in jjj and get a:%s' % (a)) return 'fun2 over' jjj('aaaaaa') print(jjj('aaaaaa')) # 運行這步fun2 over 纔打印了
---in jjj and get a:aaaaaa
---0.0
---in jjj and get a:aaaaaa
---0.0
---fun2 over函數
def wrapper1(func): def inner1(): print('wrapper1 ,before func') # 2 func() # 如今這裏變成了f() print('wrapper1 ,after func') # 4 return inner1 def wrapper2(func): def inner2(): print('wrapper2 ,before func') # 1 func() # 如今這裏成了inner1() print('wrapper2 ,after func') # 5 return inner2 @wrapper2 # f = wrapper2(f) 裏面的f==inner1 外面的f == inner2 @wrapper1 # f = wrapper1(f) 裏面的f==函數名f 外面的f == inner1
# 即inner2 = wrapper2(wrapper1(f)),先執行裏面的wrapper1(f),return成了inner1,即inner2 = wrapper2(inner1),
執行wrapper2(inner1),return成了inner2,執行inner2函數,下來在執行inner1函數,在執行f()函數 def f(): # 3 print('in f') f()
---wrapper2 ,before func
---wrapper1 ,before func
---in f
---wrapper1 ,after func
---wrapper2 ,after func性能
# 優先執行靠近裝飾函數的語法糖,即wrapper1測試
整個語法糖自上而下,先執行外層的wrapper2,wrapper1,spa
在執行wrapper1下的函數體,再執行裏層的wrapper1,wrapper2日誌
def outer(flag): # outer(True) def timer(func): # timer(func) def inner(*args, **kwargs): if flag: print('''執行函數以前要作的''') re = func(*args, **kwargs) if flag: print('''執行函數以後要作的''') return re return inner return timer @outer(True) # func = outer(True)(func) def func(): print(111) func() # 這裏裝飾器所帶參數爲True,若是把它改成false則裝飾器不起裝飾做用,至關於去除了裝飾功能。
--------------------------記住了,這是固定套路---------------------------------
# 通用裝飾器 def wrapper(func): def inner(*args, **kwargs): # 傳入萬能參數 '''執行函數前的操做''' ret = func(*args, **kwargs) '''執行函數後的操做''' return ret # 將函數的值返回 return inner # 添加裝飾器 @wrapper def func(): print('通用裝飾器') # 調用函數 func()
import functools def wrapper(func): @functools.wraps(func) def inner(*args,**kwargs): return func(*args,**kwargs) return inner @wrapper def f1(): print('f1') @ wrapper def f2(): print('f2') print(f1.__name__) # f1 print(f2.__name__) # f2
關於functools模塊的使用,這裏因爲使用了@functools.wraps(func),原本二者打印的都是inner,可是因爲使用了它,輸出f1,f2code
做業題
1.寫函數,返回一個撲克牌列表,裏面有52項,每一項是一個元組
例如:[(‘紅心’,2),(‘草花’,2), …(‘黑桃’,‘A’)]
def cards(): s = [(i, j) for i in ['黑', '紅', '梅', '方'] for j in range(1, 14)] return s print(cards()) def cards(args): li = [] for i in args: for j in range(1, 14): li.append((i, j)) print(li) cards(['黑', '紅', '梅', '方']) 標配呀,這個 def cards(*args): li = [] for i in ['黑', '紅', '梅', '方']: for j in args: li.append((i, j)) return li print(cards(*(list(range(1, 11))), *['A', 'J', 'Q', 'K']))
2.寫函數,傳入n個數,返回字典{‘max’:最大值,’min’:最小值}
例如:min_max(2,5,7,8,4)
返回:{‘max’:8,’min’:2}
def min_max(*args): return dict([('max', max(args)), ('min', min(args))]) print(min_max(515,15,6,13,45))
3.寫函數,專門計算圖形的面積
其中嵌套函數,計算圓的面積,正方形的面積和長方形的面積
調用函數area(‘圓形’,圓半徑) 返回圓的面積
調用函數area(‘正方形’,邊長) 返回正方形的面積
調用函數area(‘長方形’,長,寬) 返回長方形的面積
def area():
def 計算長方形面積():
pass
def 計算正方形面積():
pass
def 計算圓形面積():
pass
def area(*args): def inner(): if args[0] == '長方形': return args[1]*args[2] if args[0] == '正方形': return args[1]*args[1] if args[0] == '圓形': return args[1]*args[1]*3.14 return inner() print(area('圓形', 3))
4.寫函數,傳入一個參數n,返回n的階乘
例如:cal(7)
計算7*6*5*4*3*2*1
def cal(num): s = 1 for i in range(num, 0, -1): s = s*i return s print(cal(7)) def cal(num): if num == 1: return 1 return num*cal(num-1) print(cal(5))
5.給每一個函數寫一個記錄日誌的功能,
功能要求:每一次調用函數以前,要將函數名稱,時間節點記錄到log的日誌中。
所需模塊:
import time
struct_time = time.localtime()
print(time.strftime("%Y-%m-%d %H:%M:%S",struct_time))
import time def func(func2): def inner(): struct_time = time.localtime() with open('log.txt', mode='a', encoding='utf-8') as f: f.write('{}\t{}\n'.format(func2.__name__, time.strftime("%Y-%m-%d %H:%M:%S", struct_time))) func2() return inner @func def func1(): pass func1()
六、編寫裝飾器,爲多個函數加上認證的功能(用戶的帳號密碼來源於文件),要求登陸成功一次,後續的函數都無需再輸入用戶名和密碼
dic = { 'username':None, 'status':False, } def wrapper(func): def inner(*args, **kwargs): if dic['status']: # 開始是False,因此先走else,用戶帳戶密碼正確下,這裏改成True,就一直執行這步 ret = func(*args, **kwargs) return ret else: i = 0 while i < 3: username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() with open('register_msg', encoding='utf-8') as f1: for j in f1: j_li = j.strip().split() # ['張三','123'] if username == j_li[0] and password == j_li[1]: dic['username'] = username dic['status'] = True ret = func(*args, **kwargs) return ret else: # 注意這裏的else縮進,若是在if下面則會循環打印 print('帳號或者密碼錯誤,請從新輸入%s機會' % (2-i)) i += 1 return inner @wrapper def article(): print('文章') @wrapper def diary(): print('日記') @wrapper def comment(): print('評論') @wrapper def file(): print('文件') article() diary() comment() file()
7.在編寫裝飾器,爲多個函數加上認證的功能(用戶的帳號密碼來源於文件),要求登陸成功一次,後續的函數都無需再輸入用戶名和密碼。這個做業之上進行升級操做:
設置兩套密碼,一套爲京東帳號密碼,一套爲淘寶帳號密碼保存在文件中。
設置四個函數,分別表明 京東首頁,京東超市,淘寶首頁,淘寶超市。
循環打印四個選項:東首頁,京東超市,淘寶首頁,淘寶超市。
供用戶選擇,用戶輸入選項後,執行該函數,四個函數都加上認證功能,只要登錄成功一次,在選擇其餘函數,後續都無需輸入用戶名和密碼。
相關提示:用帶參數的裝飾器。裝飾器內部加入判斷,驗證不一樣的帳戶密碼。
# user_pwd文件內容: {'微信':{'username':'老男孩', 'password': '123'}, # 'qq':{'username':'老男孩1', 'password': '123'},} dic = { 'username':None, 'status':False, } def login(flag): def wrapper(func): def inner(*args, **kwargs): if dic['status']: ret = func(*args, **kwargs) return ret else: i = 0 while i < 3: username = input('請輸入用戶名(用%s帳號):' % flag).strip() password = input('請輸入密碼:').strip() with open('user_pwd',encoding='utf-8') as f1: msg_dic = eval(f1.readline()) # 經過eval函數強制性將字符串轉換成了字典 # {'微信': {'password': '123', 'username': '老男孩'}, # 'qq': {'password': '123', 'username': '老男孩1'}} if username == msg_dic[flag]['username'] and password == msg_dic[flag]['password']: dic['username'] = username dic['status'] = True ret = func(*args, **kwargs) return ret else: print('您輸入的用戶或者密碼錯誤,請從新輸入,還有%s次機會' % (2-i)) i += 1 return inner return wrapper @login('微信') def taobao_home(): print('淘寶首頁') @login('微信') def taobao_shop(): print('淘寶超市') @login('qq') def jingdong_home(): print('京東首頁') @login('qq') def jingdong_shop(): print('京東超市') choice_dict = { 1: taobao_home, 2: taobao_shop, 3: jingdong_home, 4: jingdong_shop, } while True: print('1 淘寶首頁\n2 淘寶超市\n3 京東首頁\n4 京東超市') choice_num = input('請選擇輸入的序號:').strip() if choice_num.isdigit(): choice_num = int(choice_num) if 0 < choice_num <= len(choice_dict): choice_dict[choice_num]() else: print('請輸入範圍內的序號') else: print('您輸入的有非法字符,請從新輸入')