開閉原則:
在不修改原函數及其調用方式的狀況下對原函數功能進行擴展
對代碼的修改是封閉
不能修改被裝飾的函數的源代碼
不能修改被裝飾的函數的調用方式python
用函數的方式設想一下游戲裏用槍的場景web
1 def game(): 2 print('壓子彈') 3 print('槍上膛') 4 print('發射子彈') 5 game() 6 game() 7 game() 8 9 此時須要給槍增長一個瞄準鏡,好比狙擊遠程目標時候須要加,狙擊近程目標不用加 10 此時上邊的代碼就變成了如今的代碼 11 12 def sight(): 13 print('專業狙擊瞄準鏡') 14 game() 15 sight() 16 sight() 17 sight() 18 此時的設計就不符合開閉原則(由於修改了原代碼及調用名稱)
裝飾器(python裏面的動態代理)
本質: 是一個閉包
組成: 函數+實參高階函數+返回值高階函數+嵌套函數+語法糖 = 裝飾器
存在的意義: 在不破壞原有函數和原有函數調用的基礎上,給函數添加新的功能閉包
通用裝飾器寫法:app
1 def warpper(fn): # fn是目標函數至關於func 2 def inner(*args,**kwargs): # 爲目標函數的傳參 3 '''在執行目標函數以前操做''' 4 ret = fn(*args,**kwargs) # 調用目標函數,ret是目標函數的返回值 5 '''在執行目標函數以後操做''' 6 return ret # 把目標函數返回值返回,保證函數正常的結束 7 return inner 8 9 #語法糖 10 @warpper #至關於func = warpper(func) 11 def func(): 12 pass 13 func() #此時就是執行的inner函數
上邊的場景用裝飾器修改後函數
1 方式一 2 def game(): 3 print('壓子彈') 4 print('槍上膛') 5 print('發射子彈') 6 7 def sight(fn): # fn接收的是一個函數 8 def inner(): 9 print('安裝專業狙擊瞄準鏡') 10 fn() #調用傳遞進來的函數 11 print('跑路') 12 return inner #返回函數地址 13 14 game = sight(game) #傳遞game函數到sight函數中 15 game() 16 17 執行步驟 18 第一步定義兩個函數game()爲普通函數,sight()爲裝飾器函數 19 第二步定義game = sight(game)等於把game函數當作參數傳遞給sight(fn)裝飾器函數fn形參 20 第三步執行sight(fn),fn在形參位置,至關於下邊函數game()傳參過來等於fn 21 第四步執行inner函數,而後return把inner函數內存地址當作返回值返回給sight(game) 22 第五步而後執行game(),至關於執行inner函數 23 第六步,執行inner函數,打印'狙擊鏡',執行fn()形參,因爲fn形參等於game函數,因此執行game()函數,打印'壓子彈','上膛','發射子彈' 24 第七步打印'跑路' 25 第八步把打印的結果返回給game() 26 27 方式二 28 def sight(fn): # fn接收的是一個函數 29 def inner(): 30 print('安裝專業狙擊瞄準鏡') 31 fn() #調用傳遞進來的函數 32 print('跑路') 33 return inner #返回函數地址 34 35 @sight #至關於game = sight(game) 36 def game(): 37 print('壓子彈') 38 print('槍上膛') 39 print('發射子彈') 40 game() 41 42 執行步驟 43 第一步執行sight(fn)函數 44 第二步執行@sight,至關於把把game函數與sight裝飾器作關聯 45 第三步把game函數當作參數傳遞給sight(fn)裝飾器函數fn形參 46 第四步執行inner函數,而後return把inner函數內存地址當作返回值返回給@sight 47 第五步執行game()至關至關於執行inner()函數,由於@sight至關於game = sight(game) 48 第六步打印'瞄準鏡 49 第七步執行fn函數,由於fn等於game函數,因此會執行game()函數,打印'壓子彈','上膛','發射子彈'.fn()函數執行完畢 50 第八步打印'跑路' 51 第九步而後把全部打印的結果返回給game() 52 53 結果 54 安裝專業狙擊瞄準鏡 55 壓子彈 56 槍上膛 57 發射子彈 58 跑路
一個簡單的裝飾器實現測試
1 def warpper(fn): 2 def inner(): 3 print('每次執行被裝飾函數以前都要先通過這裏') 4 fn() 5 return inner 6 @warpper 7 def func(): 8 print('執行了func函數') 9 func() 10 11 結果 12 每次執行被裝飾函數以前都要先通過這裏 13 執行了func函數
帶有一個或多個參數的裝飾器this
1 def sight(fn): #fn等於調用game函數 2 def inner(*args,**kwargs): #接受到的是元組("bob",123) 3 print('開始遊戲') 4 fn(*args,**kwargs) #接受到的全部參數,打散傳遞給user,pwd 5 print('跑路') 6 return inner 7 @sight 8 def game(user,pwd): 9 print('登錄遊戲用戶名密碼:',user,pwd) 10 print('壓子彈') 11 print('槍上膛') 12 print('發射子彈') 13 game('bob','123') 14 結果 15 開始遊戲 16 登錄遊戲用戶名密碼: bob 123 17 壓子彈 18 槍上膛 19 發射子彈 20 跑路
動態傳遞一個或多個參數給裝飾器spa
1 def sight(fn): #調用game函數 2 def inner(*args,**kwargs): #接受到的是元組("bob",123) 3 print('開始遊戲') 4 fn(*args,**kwargs) #接受到的全部參數,打散傳遞給正常的參數 5 print('跑路') 6 return inner 7 @sight 8 def game(user,pwd): 9 print('登錄遊戲用戶名密碼:',user,pwd) 10 print('壓子彈') 11 print('槍上膛') 12 print('發射子彈') 13 return '遊戲展現完畢' 14 ret = game('bob','123') #傳遞了兩個參數給裝飾器sight 15 print(ret) 16 17 @sight 18 def car(qq): 19 print('登錄QQ號%s'%qq) 20 print('開始戰車遊戲') 21 ret2 = car(110110) #傳遞了一個參數給裝飾器sight 22 print(ret2) 23 結果 24 開始遊戲 25 登錄遊戲用戶名密碼: bob 123 26 壓子彈 27 槍上膛 28 發射子彈 29 跑路 30 None 31 開始遊戲 32 登錄QQ號110110 33 開始戰車遊戲 34 跑路 35 None 36 你會發現這兩個函數執行的返回值都爲None,可是我game定義返回值了return '遊戲展現完畢',卻沒給返回
裝飾器的返回值設計
1 爲何我定義了返回值,可是返回值仍是None呢,是由於我即便在game函數中定義了return '遊戲展現完畢' 2 可是裝飾器裏只有一個return inner定義返回值,可是這個返回值是返回的inner函數的內存地址的,並非inner 3 函數內部的return因此默認爲None,因此應該定義一個inner函數內部的return返回值,並且也沒有接收返回值的變量, 4 因此要要設置ret = fn(*args,**kwargs)和return ret 5 6 def sight(fn): #調用game函數 7 def inner(*args,**kwargs): #接受到的是元組("bob",123) 8 print('開始遊戲') 9 ret = fn(*args,**kwargs) #接受到的全部參數,打散傳遞給正常的參數 10 print('跑路') 11 return ret 12 return inner 13 @sight 14 def game(user,pwd): 15 print('登錄遊戲用戶名密碼:',user,pwd) 16 print('壓子彈') 17 print('槍上膛') 18 print('發射子彈') 19 return '遊戲展現完畢' 20 ret = game('bob','123') #傳遞了兩個參數給裝飾器sight 21 print(ret) 22 結果 23 開始遊戲 24 登錄遊戲用戶名密碼: bob 123 25 壓子彈 26 槍上膛 27 發射子彈 28 跑路 29 遊戲展現完畢 30 31 32 事例2 33 def wrapper_out(flag): #裝飾器自己的參數 34 def wrapper(fn): #目標函數 35 def inner(*args,**kwargs): #目標函數須要接受的參數 36 if flag == True: 37 print('找第三方問問價格行情') 38 ret = fn(*args,**kwargs) 39 print('買到裝備') 40 return ret 41 else: 42 ret = fn(*args,**kwargs) 43 return ret 44 return inner 45 return wrapper 46 #語法糖,@裝飾器 47 @wrapper_out(True) 48 def func(a,b): #被wrapper裝飾 49 print(a,b) 50 print('開黑') 51 return 'func返回值' 52 abc = func('我是參數1','我是參數2') 53 print(abc) 54 結果 55 找第三方問問價格行情 56 我是參數1 我是參數2 57 開黑 58 買到裝備 59 func返回值
多個裝飾器同用一個函數代理
1 def wrapper1(fn): 2 def inner(*args,**kwargs): 3 print('wrapper1-1') 4 ret = fn(*args,**kwargs) 5 print('wrapper1-2') 6 return ret 7 return inner 8 9 def wrapper2(fn): 10 def inner(*args,**kwargs): 11 print('wrapper2-1') 12 ret = fn(*args,**kwargs) 13 print('wrapper2-2') 14 return ret 15 return inner 16 17 def wrapper3(fn): 18 def inner(*args,**kwargs): 19 print('wrapper3-1') 20 ret = fn(*args,**kwargs) 21 print('wrapper3-2') 22 return ret 23 return inner 24 @wrapper1 25 @wrapper2 26 @wrapper3 27 def func(): 28 print('我是測試小白') 29 func() 30 結果 31 wrapper1-1 32 wrapper2-1 33 wrapper3-1 34 我是測試小白 35 wrapper3-2 36 wrapper2-2 37 wrapper1-2