裝飾器(Decorator)本質是函數,功能是爲其餘函數添加附加功能,定義一個裝飾器須要知足如下兩個原則:python
裝飾器 = 高階函數 + 函數嵌套 + 閉包數據庫
高階函數定義:閉包
知足以上任意一個條件就是高階函數。app
def calc(): print('calc 運行完畢~') f = calc f()
因爲函數也是對象,所以函數也能夠賦值給一個變量,調用這個變量就是調用函數。函數對象有一個 __name__
屬性,用於查看函數的名字:框架
calc.__name__ f.__name__ 'calc' 'calc'
需求:函數
在不修改 calc
源代碼的前提下,爲 calc
函數添加一個代碼計時功能(計算 calc
運行時間)。網站
使用高階函數模擬裝飾器實現calc
計時功能。設計
import time def timmer(func): start_time = time.time() func() stop_time = time.time() print('函數 %s 運行時間爲:%s' % (func.__name__, stop_time - start_time)) def calc(): time.sleep(2) print('calc 運行完畢~') timmer(calc)
calc 運行完畢~ calc 運行時間爲:2.0011146068573
雖然爲 calc
增長了計時功能,但 calc
原來的執行方式是 calc()
,而如今是調用高階函數timmer(calc)
,改變了調用方式。日誌
import time def timmer(func): # func: calc start_time = time.time() func() # func(): calc() stop_time = time.time() print('函數 %s 運行時間爲:%s' % (func.__name__, stop_time - start_time)) return func # func: calc (<function calc at 0x00000000052D6D90>) 內存地址 def calc(): time.sleep(2) print('calc 運行完畢~') calc = timmer(calc) # calc = timmer(calc): calc = <function calc at 0x00000000052D6D90> calc()
calc 運行完畢~ calc 的運行時間是:2.0001144409179688 calc 運行完畢~
沒有改變 calc
的調用方式,但也沒爲其添加新功能。code
使用高階函數實現裝飾器功能 :
函數中嵌套另外一個函數
def father(name): print('I am %s' % name) def son(): print('My father is %s' % name) def grandson(): print('My grandfather is %s' % name) grandson() son() father('tom')
I am tom My father is tom My grandfather is tom
閉包也是函數嵌套,若是在一個內部函數裏調用外部做用域(不是全局做用域)的變量,那麼這個內部函數就是閉包(closure)
def father(name): print('I am %s' % name) def son(): name = 'john' def grandson(): print('My father is %s' % name) grandson() son() father('tom')
I am tom My father is john
內部函數 grandson 調用了它的外部函數 son 的變量 name='john'
,那麼 grandson就是一個閉包。
無參數裝飾器 = 高階函數 + 函數嵌套
# 實現一個裝飾器的基本框架 def timmer(func): def wrapper(): func() return wrapper
def timmer(func): def wrapper(*args, **kwargs): func(*args, **kwargs) return wrapper
import time def timmer(func): def wrapper(*args, **kwargs): """計時功能""" start_time = time.time() func(*args, **kwargs) stop_time = time.time() print('函數 %s 運行時間:%s' % (func, stop_time - start_time)) return wrapper
import time def timmer(func): def wrapper(*args, **kargs): start_time = time.time() res = func(*args, **kwargs) stop_time = time.time() print('函數 %s 運行時間:%s' % (func, stop_time - start_time)) return res return wrapper
def calc(): time.sleep(2) print('calc 運行完畢~') return 'calc 返回值' calc = timmer(calc) calc()
@timmer # 至關於 calc = timmer(calc) def calc(): time.sleep(2) print('calc 運行完畢~') calc()
使用無參數裝飾器,爲 calc
添加計時功能(統計 calc
代碼運行時間)
import timmer def timmer(func): # func: calc """裝飾器函數""" def wrapper(*args, **kwargs): # args:<class 'tuple'>:('rose', 18, 'female') start_time = time.time() # kwargs={} res = func(*args, **kwargs) # res: 'calc 返回值' stop_time = time.time() print('函數 %s 運行時間:%s' % (func.__name__, stop_time - start_time)) return res # func: calc (<function calc at 0x00000000052D6D90>) 內存地址 return wrapper @timmer # @timmer: calc=timmer(calc) ——>calc = <function wrapper at 0x00000000052D6D90> def calc(name, age, gender): """被修飾函數""" time.sleep(2) print('calc 運行完畢~') print('名字:%s,年齡:%d,性別:%s' % (name, age, gender)) return 'calc 返回值' s = calc('rose', 18, 'female') # 至關於執行 s = wrapper('rose', 18, 'female') print(s)
calc 運行完畢 名字:rose,年齡:18,性別:female 函數 calc 運行時間:2.0001144409179688 calc 返回值
因爲 timmer()
是一個裝飾器函數,返回一個函數 wrapper
。因此 calc()
函數仍然存在,只是如今同名的 calc
變量指向了新的函數,因而調用 calc()
執行的是wrapper()
函數。
有參數裝飾器 = 高階函數 + 函數嵌套 + 閉包
若是裝飾器本山須要傳入參數,就須要再編寫一個 decorator
的高階函數。好比給 calc
函數添加一個日誌功能,可以打印日誌,其基本框架以下:
def log(text): def timmer(func): def wrapper(*args, **kwargs): func(*args, **kwargs) return wrapper return timmer @log('文本內容') def calc(): pass
示例:
import timmer def log(text): # text: '文本內容' def timmer(func): # func: calc """裝飾器函數""" def wrapper(*args, **kwargs): # args:<class 'tuple'>:('rose', 18, 'female') start_time = time.time() # kwargs={} res = func(*args, **kwargs) # res: 'calc 返回值' stop_time = time.time() print('函數 %s 運行時間:%s' % (func.__name__, stop_time - start_time)) return res # func: calc (<function calc at 0x00000000052D6D90>) 內存地址 return wrapper return timmer @log('文本內容') # 至關於 calc = log('自定義文本')(calc) ——> timmer(calc) ——> calc=wrapper def calc(name, age, gender): """被修飾函數""" time.sleep(2) print('calc 運行完畢~') print('名字:%s,年齡:%d,性別:%s' % (name, age, gender)) return 'calc 返回值' s = calc('rose', 18, 'female') # 至關於執行 s = wrapper('rose', 18, 'female') print(s)
與兩層嵌套效果的decorator
相比,三層效果是這樣的:
calc = log('自定義文本')(calc) # 即 calc = wrapper
首先執行 log('自定義文本')
,返回timmer
函數,再調用返回參數(timmer(calc)
),參數是 calc
,返回值是wrapper
函數,最後再調用wrapper()
。
函數也是對象,也有__name__
屬性(返回函數名)。但通過裝飾的calc
函數,它的__name__
從原來的calc
變成了wrapper
。
>>> calc.__name__ 'wrapper'
有些須要依賴函數簽名的代碼由於__name__
改變,而出現某些錯誤,因此須要將原calc
的__name__
屬性複製到wrapper()
函數中。
import functools .... @functools.wraps(func) def wrapper(*args, **kwargs) ...
實例 1
請設計一個decorator,它可做用於任何函數上,並打印該函數的執行時間:
import time import functools def metric(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): start_time = time.time() res = fn(*args, **kwargs) stop_time =time.time() print('%s executed in %s ms' % (fn.__name__, stop_time - start_time)) return res return wrapper @metric # fast=metric(calc) def fast(x, y): time.sleep(0.0012) return x + y @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z f = fast(11, 22) s = slow(11, 22, 33) print(f, s)
fast executed in 0.0019998550415039062 ms slow executed in 0.1240072250366211 ms 33 7986
實例 2
實現一個購物網站基本功能,其功能以下:
import time import functools # 模擬存儲用戶名、密碼數據庫 user_list=[ {'name':'alex','passwd':'123'}, {'name':'linhaifeng','passwd':'123'}, {'name':'wupeiqi','passwd':'123'}, {'name':'yuanhao','passwd':'123'}, ] current_dic = {'username': None, 'login': False} # 用於保存登陸記錄,None,False 爲沒有登陸 def auth_func(func): """裝飾器函數""" @functools.wraps(func) def wrapper(*args, **kwargs): # 僅供 home、shopping 調用(由於不須要再輸入用戶名和密碼) if current_dic['username'] and current_dic['login']: res = func(*args, **kwargs) return res username = input('用戶名:').strip() passwd = input('密碼:').strip() # 遍歷循環用戶名、密碼數據庫,比對用戶輸入的用戶名和密碼 for user_dic in user_list: if username == user_dic['name'] and passwd == user_dic['passwd']: # 用戶保持會話,即保持用戶登陸狀態,賦值 current_dic['username'] = username current_dic['login'] = True res = func(*args, **kwargs) return res else: print('用戶名或密碼錯誤') return wrapper @auth_func def index(): """主頁(須要登陸)""" print('歡迎來到xxx主頁!') @auth_func def home(name): """用戶登陸成功後的界面""" print('歡迎回家 %s' % name) @auth_func def shopping_car(name): """購物車""" print('%s購物車裏有:[%s,%s,%s]' % (name, '遊艇', '車子', '飛機')) print('before-->',current_dic) index() print('after-->',current_dic) home('rose') shopping_car('rose')
before--> {'username': None, 'login': False} 用戶名:alex 密碼:123 歡迎來到京東主頁! after--> {'username': 'alex', 'login': True} 歡迎回家 rose rose購物車裏有:[遊艇,車子,飛機]
帶參數裝飾器(須要認證類型):
import time import functools user_list=[ {'name':'alex','passwd':'123'}, {'name':'linhaifeng','passwd':'123'}, {'name':'wupeiqi','passwd':'123'}, {'name':'yuanhao','passwd':'123'}, ] current_dic={'username':None,'login':False} def auth(auth_type='filedb'): def auth_func(func): @funtools.wraps(func) def wrapper(*args,**kwargs): print('認證類型是',auth_type) if auth_type == 'filedb': if current_dic['username'] and current_dic['login']: res = func(*args, **kwargs) return res username=input('用戶名:').strip() passwd=input('密碼:').strip() for user_dic in user_list: if username == user_dic['name'] and passwd == user_dic['passwd']: current_dic['username']=username current_dic['login']=True res = func(*args, **kwargs) return res else: print('用戶名或者密碼錯誤') elif auth_type == 'ldap': print('ldap 認證類型') res = func(*args, **kwargs) return res else: print('不知道什麼認證方式') res = func(*args, **kwargs) return res return wrapper return auth_func # 至關於 auth_func = auth(auth_type='filedb')(auth_func) @auth(auth_type='filedb') def index(): print('歡迎來到xxx主頁') @auth(auth_type='ldap') def home(name): print('歡迎回家%s' %name) # @auth(auth_type='sssssss') def shopping_car(name): print('%s的購物車裏有[%s,%s,%s]' %(name,'奶茶','妹妹','娃娃')) # print('before-->',current_dic) # index() # print('after--->',current_dic) # home('產品經理') shopping_car('產品經理')