【目錄】
一、裝飾器的介紹
1、什么是裝飾器
2、為何要用裝飾器
二、裝飾器的實作
1、無參裝飾器的實作
2、有參裝飾器的實作
*儲備知識:(請點擊下方標題閱讀哦~)
1、名稱空間和作用域
2、函式物件+函式的嵌套+閉包函式
一、裝飾器的介紹
1、為何要用裝飾器
開放封閉原則——
軟體的設計應該遵循開放封閉原則,即對擴展是開放的,而對修改是封閉的,
對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況,
對修改封閉,意味著物件一旦設計完成,就可以獨立完成其作業,而不要對其進行修改,
軟體包含的所有功能的源代碼以及呼叫方式,都應該避免修改,否則一旦改錯,則極有可能產生連鎖反應,最終導致程式崩潰,
而對于上線后的軟體,新需求或者變化又層出不窮,我們必須為程式提供擴展的可能性,這就用到了裝飾器,
2、什么是裝飾器
’裝飾’代指為被裝飾物件添加新的功能,
’器’代指器具/工具,裝飾器與被裝飾的物件均可以是任意可呼叫物件,
即 裝飾器的作用就是在不修改被裝飾物件源代碼和呼叫方式的前提下為被裝飾物件添加額外的功能,
裝飾器經常用于有切面需求的場景,比如:插入日志、性能測驗、事務處理、快取、權限校驗等應用場景,裝飾器是解決這類問題的絕佳設計,
有了裝飾器,就可以抽離出大量與函式功能本身無關的雷同代碼并繼續重用,
提示:可呼叫物件有函式,方法或者類,此處我們單以本章主題函式為例,來介紹函式裝飾器,并且被裝飾的物件也是函式,
函式裝飾器,指的是定義一個函式,該函式是用來為其他函式添加額外的功能,
二、函式裝飾器的實作
函式裝飾器分為:無參裝飾器和有參裝飾器兩種,二者的實作原理一樣,都是’函式嵌套+閉包+函式物件’的組合使用的產物,
遵循原則:不修改被裝飾物件源代碼&呼叫方式
1、無參裝飾器的實作
(請緊跟步伐——)
(0)需求——在不修改index函式的源代碼以及呼叫方式的前提下為其添加統計運行時間的功能
def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) index(111,222) # index(y=111,x=222) #其他呼叫的引數形式 # index(111,y=222)
(1)解決方案一:失敗
問題:沒有修改被裝飾物件的呼叫方式,但是修改了其源代碼
import time def index(x,y): start=time.time() time.sleep(3) print('index %s %s' %(x,y)) stop = time.time() print(stop - start) index(111,222)
(2)解決方案二:失敗
問題:沒有修改被裝飾物件的呼叫方式,也沒有修改了其源代碼,并且加上了新功能,但是代碼冗余
import time def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) start=time.time() index(111,222) stop=time.time() print(stop - start) start=time.time() index(111,222) stop=time.time() print(stop - start) start=time.time() index(111,222) stop=time.time() print(stop - start)
(3)解決方案三:失敗
問題:解決了方案二代碼冗余問題,但帶來一個新問題即函式的呼叫方式改變了
import time def index(x,y): time.sleep(3) print('index %s %s' %(x,y)) def wrapper(): start=time.time() index(111,222) #index本身也存在問題,其引數被限定了;index引數一增減,wrapper的也需要增減——如何寫活呢? stop=time.time() print(stop - start) wrapper()
(4)優化方案三的大方向:如何在方案三的基礎上,不改變函式的呼叫方式
方案三的優化一:將index的引數寫活了
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def wrapper(*args,**kwargs): start=time.time() index(*args,**kwargs) # index(3333,z=5555,y=44444);wrapper的引數,是傳給index使用的,使用該種方式,就可接收任意形式的引數,從而避免修改wrapper的引數 stop=time.time() print(stop - start) # wrapper(3333,4444,5555) # wrapper(3333,z=5555,y=44444)
方案三的優化二:在優化一的基礎上把被裝飾物件寫活了,原來只能裝飾index……
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def home(name): time.sleep(2) print('welcome %s to home page' %name) def outter(func): # func = index的記憶體地址 def wrapper(*args,**kwargs): start=time.time() func(*args,**kwargs) # index的記憶體地址() stop=time.time() print(stop - start) return wrapper
#為何要有該行代碼?return wrapper—因為wrapper的記憶體地址為全域的,現在被裝進了一個函式里,放在了‘區域’,因此未傳值,需回傳它的記憶體地址 (名字,即為其記憶體地址) index=outter(index) # index=wrapper的記憶體地址 home=outter(home) # home=wrapper的記憶體地址 home('egon') # home(name='egon')
方案三的優化三:將wrapper做的跟被裝飾物件一模一樣,以假亂真
import time def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) def home(name): time.sleep(2) print('welcome %s to home page' %name) def outter(func): def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print(stop - start) return res return wrapper # 偷梁換柱:home這個名字指向的wrapper函式的記憶體地址 home=outter(home) res=home('egon') # res=wrapper('egon') print('回傳值--》',res)
(5)語法糖——讓你開心的語法糖,可以含在嘴里很久很久
如何定義并使用裝飾器——
== 定義裝飾器
== 使用裝飾器:在被裝飾物件正上方的單獨一行寫@裝飾器名字
注意:名字后面加括號,就會觸發函式體的執行,@裝飾器名字,即 被裝飾物件名=裝飾器名字(被裝飾物件名)
括號的優先級高于@,即 @裝飾器名字(),先執行括號,后為@,但會報錯,
糖栗子1:統計運行時間裝飾器 timmer
import time #定義裝飾器 def timmer(func): def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print(stop - start) return res return wrapper # 使用裝飾器:在被裝飾物件正上方的單獨一行寫@裝飾器名字 @timmer # index=timmer(index) def index(x,y,z): time.sleep(3) print('index %s %s %s' %(x,y,z)) @timmer # home=timmer(ome) def home(name): time.sleep(2) print('welcome %s to home page' %name) index(x=1,y=2,z=3) home('egon')
(6)總結無參裝飾器模板
模板:
def outter(func): def wrapper(*args,**kwargs): # 1、呼叫原函式 # 2、為其增加新功能 res=func(*args,**kwargs) return res return wrapper
糖栗子2:登錄系統認證功能裝飾器 auth
def auth(func): def wrapper(*args, **kwargs): # 1、呼叫原函式 # 2、為其增加新功能 name = input('your name>>: ').strip() pwd = input('your password>>: ').strip() if name == 'egon' and pwd == '123': res = func(*args, **kwargs) return res else: print('賬號密碼錯誤') return wrapper @auth def index(): print('from index') index()
(7)吃完糖,思考一下——疊加多個裝飾器時,加載順序與運行順序是怎樣的?
@deco1 # index=deco1(deco2.wrapper的記憶體地址) @deco2 # deco2.wrapper的記憶體地址=deco2(deco3.wrapper的記憶體地址) @deco3 # deco3.wrapper的記憶體地址=deco3(index) ——加載裝飾器:自下而上 def index(): pass
# 疊加多個裝飾器
1. 加載順序(outter函式的呼叫順序):自下而上
2. 執行順序(wrapper函式的執行順序):自上而下栗子如下:
def outter1(func1): #func1=wrapper2的記憶體地址 print('加載了outter1') def wrapper1(*args,**kwargs): print('執行了wrapper1') res1=func1(*args,**kwargs) return res1 return wrapper1 def outter2(func2): #func2=wrapper3的記憶體地址 print('加載了outter2') def wrapper2(*args,**kwargs): print('執行了wrapper2') res2=func2(*args,**kwargs) return res2 return wrapper2 def outter3(func3): # func3=最原始的那個index的記憶體地址 print('加載了outter3') def wrapper3(*args,**kwargs): print('執行了wrapper3') res3=func3(*args,**kwargs) return res3 return wrapper3 @outter1 # outter1(wrapper2的記憶體地址)======>index=wrapper1的記憶體地址 @outter2 # outter2(wrapper3的記憶體地址)======>wrapper2的記憶體地址 @outter3 # outter3(最原始的那個index的記憶體地址)===>wrapper3的記憶體地址 def index(): print('from index') print('======================================================') index()
(8)補充一下:from functools import wraps
#偷梁換柱,即將原函式名指向的記憶體地址偷梁換柱成wrapper函式,所以應該將wrapper做的跟原函式一樣才行
糖栗子3:
from functools import wraps def outter(func): @wraps(func) def wrapper(*args, **kwargs): """這個是主頁功能""" res = func(*args, **kwargs) # res=index(1,2) return res # 手動將原函式的屬性賦值給wrapper函式 # 1、函式wrapper.__name__ = 原函式.__name__ # 2、函式wrapper.__doc__ = 原函式.__doc__ # wrapper.__name__ = func.__name__ # wrapper.__doc__ = func.__doc__ return wrapper @outter # index=outter(index) def index(x,y): """這個是主頁功能""" print(x,y) print(index.__name__) print(index.__doc__) #help(index)
2、有參裝飾器的實作
(1)知識儲備
由于語法糖@的限制,outter函式只能有一個引數,并且該引數只用來接收被裝飾物件的記憶體地址
def outter(func): # func = 函式的記憶體地址 def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrapper # @outter # index=outter(index) # index=>wrapper @outter # outter(index) def index(x,y): print(x,y)
偷梁換柱之后——
index的引數什么樣子,wrapper的引數就應該什么樣子
index的回傳值什么樣子,wrapper的回傳值就應該什么樣子
index的屬性什么樣子,wrapper的屬性就應該什么樣子==》from functools import wraps
(2) 登錄系統認證功能——山炮玩法一:
def auth(func,db_type): def wrapper(*args, **kwargs): name=input('your name>>>: ').strip() pwd=input('your password>>>: ').strip() if db_type == 'file': print('基于檔案的驗證') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) return res else: print('user or password error') elif db_type == 'mysql': print('基于mysql的驗證') elif db_type == 'ldap': print('基于ldap的驗證') else: print('不支持該db_type') return wrapper # @auth # 賬號密碼的來源是檔案 def index(x,y): print('index->>%s:%s' %(x,y)) # @auth # 賬號密碼的來源是資料庫 def home(name): print('home->>%s' %name) # @auth # 賬號密碼的來源是ldap def transfer(): print('transfer') index=auth(index,'file') home=auth(home,'mysql') transfer=auth(transfer,'ldap') # index(1,2) # home('egon') # transfer()
山炮玩法二:
def auth(db_type): def deco(func): def wrapper(*args, **kwargs): name=input('your name>>>: ').strip() pwd=input('your password>>>: ').strip() if db_type == 'file': print('基于檔案的驗證') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) return res else: print('user or password error') elif db_type == 'mysql': print('基于mysql的驗證') elif db_type == 'ldap': print('基于ldap的驗證') else: print('不支持該db_type') return wrapper return deco deco=auth(db_type='file') @deco # 賬號密碼的來源是檔案 def index(x,y): print('index->>%s:%s' %(x,y)) deco=auth(db_type='mysql') @deco # 賬號密碼的來源是資料庫 def home(name): print('home->>%s' %name) deco=auth(db_type='ldap') @deco # 賬號密碼的來源是ldap def transfer(): print('transfer') index(1,2) home('egon') transfer()
語法糖—版本:@auth(引數)
def auth(db_type): def deco(func): def wrapper(*args, **kwargs): name = input('your name>>>: ').strip() pwd = input('your password>>>: ').strip() if db_type == 'file': print('基于檔案的驗證') if name == 'egon' and pwd == '123': res = func(*args, **kwargs) # index(1,2) return res else: print('user or password error') elif db_type == 'mysql': print('基于mysql的驗證') elif db_type == 'ldap': print('基于ldap的驗證') else: print('不支持該db_type') return wrapper return deco @auth(db_type='file') # @deco # index=deco(index) # index=wrapper def index(x, y): print('index->>%s:%s' % (x, y)) @auth(db_type='mysql') # @deco # home=deco(home) # home=wrapper def home(name): print('home->>%s' % name) @auth(db_type='ldap') # 賬號密碼的來源是ldap def transfer(): print('transfer') index(1, 2) home('egon') transfer()
(3)有參裝飾器模板
def 有參裝飾器(x,y,z): def outter(func): def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper return outter @有參裝飾器(1,y=2,z=3) def 被裝飾物件(): pass
參考資料:
https://zhuanlan.zhihu.com/p/109078881
https://www.cnblogs.com/linhaifeng/articles/7532497.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/166991.html
標籤:Python
下一篇:python 協程
