什麼是開放封閉原則?有的同窗問開放,封閉這是兩個反義詞這還能組成一個原則麼?這不先後矛盾麼?其實不矛盾。開放封閉原則是分狀況討論的。python
咱們的軟件一旦上線以後(好比你的軟件主要是多個函數組成的),那麼這個軟件對功能的擴展應該是開放的,好比你的遊戲一直在迭代更新,推出新的玩法,新功能。可是對於源代碼的修改是封閉的。你就拿函數舉例,若是你的遊戲源代碼中有一個函數是閃躲的功能,那麼你這個函數確定是被多個地方調用的,好比對方扔雷,對方開槍,對方用刀,你都會調用你的閃躲功能,那麼若是你的閃躲功能源碼改變了,或者調用方式改變了,當對方發起相應的動做,你在調用你的閃躲功能,就會發生問題。因此,開放封閉原則具體具體定義是這樣:面試
1.對擴展是開放的數組
咱們說,任何一個程序,不可能在設計之初就已經想好了全部的功能而且將來不作任何更新和修改。因此咱們必須容許代碼擴展、添加新功能。微信
2.對修改是封閉的網絡
就像咱們剛剛提到的,由於咱們寫的一個函數,頗有可能已經交付給其餘人使用了,若是這個時候咱們對函數內部進行修改,或者修改了函數的調用方式,頗有可能影響其餘已經在使用該函數的用戶。OK,理解了開封封閉原則以後,咱們聊聊裝飾器。閉包
什麼是裝飾器?從字面意思來分析,先說裝飾,什麼是裝飾? 裝飾就是添加新的,好比你家剛買的房子,下一步就是按照本身的喜歡的方式設計,進行裝修,裝飾,地板,牆面,家電等等。什麼是器?器就是工具,也是功能,那裝飾器就好理解了:就是添加新的功能。app
好比我如今不會飛,怎麼才能讓我會飛?給我加一個翅膀,我就能飛了。那麼你給我加一個翅膀,它會改變我原來的行爲麼?我以前的吃喝拉撒睡等生活方式都不會改變。它就是在我原來的基礎上,添加了一個新的功能。函數
今天咱們講的裝飾器(裝修,翅膀)是以功能爲導向的,就是一個函數。工具
被裝飾的對象:好比毛坯房,我本人,其實也是一個函數。學習
因此裝飾器最終最完美的定義就是:在不改變原被裝飾的函數的源代碼以及調用方式下,爲其添加額外的功能。
接下來,咱們經過一個例子來爲你們講解這個裝飾器:
需求介紹:你如今xx科技有限公司的開發部分任職,領導給你一個業務需求讓你完成:讓你寫代碼測試小明同窗寫的函數的執行效率。
def index(): print('歡迎訪問博客園主頁')
版本1:
需求分析:你要想測試此函數的執行效率,你應該怎麼作?應該在此函數執行前記錄一個時間, 執行完畢以後記錄一個時間,這個時間差就是具體此函數的執行效率。那麼執行時間如何獲取呢? 能夠利用time模塊,有一個time.time()功能。
import time print(time.time())
此方法返回的是格林尼治時間,是此時此刻距離1970年1月1日0點0分0秒的時間秒數。也叫時間戳,他是一直變化的。因此要是計算shopping_car的執行效率就是在執行先後計算這個時間戳的時間,而後求差值便可。
import time def index(): print('歡迎訪問博客園主頁') start_time = time.time() index() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}')
因爲index函數只有一行代碼,執行效率太快了,因此咱們利用time模塊的一個sleep模擬一下
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') start_time = time.time() index() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}')
版本1分析:你如今已經完成了這個需求,可是有什麼問題沒有? 雖然你只寫了四行代碼,可是你完成的是一個測試其餘函數的執行效率的功能,若是讓你測試一下,小張,小李,小劉的函數效率呢? 你是否是全得複製:
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園首頁') def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') start_time = time.time() index() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') start_time = time.time() home('太白') end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') ......
重複代碼太多了,因此要想解決重複代碼的問題,怎麼作?咱們是否是學過函數,函數就是以功能爲導向,減小重複代碼,好咱們繼續整改。
版本2:
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') def inner(): start_time = time.time() index() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') inner()
可是你這樣寫也是有問題的,你雖然將測試功能的代碼封裝成了一個函數,可是這樣,你只能測試小明同窗的的函數index,你要是測試其餘同事的函數呢?你怎麼作?
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def inner(): start_time = time.time() index() home('太白') end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') timer()
你要是像上面那麼作,每次測試其餘同事的代碼還須要手動改,這樣是否是太low了?因此如何變成動態測試其餘函數?咱們是否是學過函數的傳參?可否將被裝飾函數的函數名做爲函數的參數傳遞進去呢?
版本3:
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timmer(func): # func == index 函數 start_time = time.time() func() # index() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') timmer(index)
這樣我將index函數的函數名做爲參數傳遞給timmer函數,而後在timmer函數裏面執行index函數,這樣就變成動態傳參了。好,大家如今將版本3的代碼快速練一遍。 你們練習完了以後,發現有什麼問題麼? 對比着開放封閉原則說: 首先,index函數除了完成了本身以前的功能,還增長了一個測試執行效率的功能,對不?因此也符合開放原則。 其次,index函數源碼改變了麼?沒有,可是執行方式改變了,因此不符合封閉原則。 原來如何執行? index() 如今如何執行? inner(index),這樣會形成什麼問題? 假如index在你的項目中被100處調用,那麼這相應的100處調用我都得改爲inner(index)。 很是麻煩,也不符合開放封閉原則。
版本4:實現真正的開放封閉原則:裝飾器。
這個也很簡單,就是咱們昨天講過的閉包,只要你把那個閉包的執行過程整清楚,那麼這個你想不會都難。
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁')
你將上面的inner函數在套一層最外面的函數timer,而後將裏面的inner函數名做爲最外面的函數的返回值,這樣簡單的裝飾器就寫好了,一點新知識都沒有加,這個若是不會就得多抄幾遍,而後理解代碼。
def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner # f = timer(index) # f()
咱們分析一下,代碼,代碼執行到這一行:f = timer(index) 先執行誰?看見一個等號先要執行等號右邊, timer(index) 執行timer函數將index函數名傳給了func形參。內層函數inner執行麼?不執行,inner函數返回 給f變量。因此咱們執行f() 就至關於執行inner閉包函數。 f(),這樣既測試效率又執行了原函數,有沒有問題?固然有啦!!版本4你要解決原函數執行方式不改變的問題,怎麼作? 因此你能夠把 f 換成 index變量就完美了! index = timer(index) index()帶着同窗們將這個流程在執行一遍,特別要注意 函數外面的index實際是inner函數的內存地址而不是index函數。讓學生們抄一遍,理解一下,這個timer就是最簡單版本裝飾器,在不改變原index函數的源碼以及調用方式前提下,爲其增長了額外的功能,測試執行效率。
你如今這個代碼,完成了最第一版的裝飾器,可是仍是不夠完善,由於你被裝飾的函數index可能會有返回值,若是有返回值,你的裝飾器也應該不影響,開放封閉原則嘛。可是你如今設置一下試試:
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner index = timer(index) print(index()) # None
加上裝飾器以後,他的返回值爲None,爲何?由於你如今的index不是函數名index,這index實際是inner函數名。因此index() 等同於inner() 你的 '訪問成功'返回值應該返回給誰?應該返回給index,這樣才作到開放封閉,實際返回給了誰?實際返回給了func,因此你要更改一下你的裝飾器代碼,讓其返回給外面的index函數名。 因此:你應該這麼作:
def timer(func): # func = index def inner(): start_time = time.time() ret = func() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return ret return inner index = timer(index) # inner print(index()) # print(inner())
藉助於內層函數inner,你將func的返回值,返回給了inner函數的調用者也就是函數外面的index,這樣就實現了開放封閉原則,index返回值,確實返回給了'index'。
讓同窗們;練習一下。
到目前爲止,你的被裝飾函數仍是沒有傳參呢?按照咱們的開放封閉原則,加不加裝飾器都不能影響你被裝飾函數的使用。因此咱們看一下。
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner # 要想timer裝飾home函數怎麼作? home = timer(home) home('太白')
上面那麼作,顯然報錯了,爲何? 你的home這個變量是誰?是inner,home('太白')實際是inner('太白')可是你的'太白'這個實參應該傳給誰? 應該傳給home函數,實際傳給了誰?實際傳給了inner,因此咱們要經過更改裝飾器的代碼,讓其將實參'太白'傳給home.
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(name): start_time = time.time() func(name) # home(name) == home('太白') end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner # 要想timer裝飾home函數怎麼作? home = timer(home) home('太白')
這樣你就實現了,還有一個小小的問題,如今被裝飾函數的形參只是有一個形參,若是要是多個怎麼辦?有人說多少個我就寫多少個不就好了,那不行呀,你這個裝飾器能夠裝飾N多個不一樣的函數,這些函數的參數是不統一的。因此你要有一種能夠接受不定數參數的形參接受他們。這樣,你就要想到*args,**kwargs。
import time def index(): time.sleep(2) # 模擬一下網絡延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name,age): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(*args,**kwargs): # 函數定義時,*表明聚合:因此你的args = ('太白',18) start_time = time.time() func(*args,**kwargs) # 函數的執行時,*表明打散:因此*args --> *('太白',18)--> func('太白',18) end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner home = timer(home) home('太白',18)
這樣利用*的打散與聚合的原理,將這些實參經過inner函數的中間完美的傳遞到給了相應的形參。
好將上面的代碼在敲一遍。
代碼優化:語法糖
根據個人學習,咱們知道了,若是想要各給一個函數加一個裝飾器應該是這樣:
def home(name,age): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner home = timer(home) home('太白',18)
若是你想給home加上裝飾器,每次執行home以前你要寫上一句:home = timer(home)這樣你在執行home函數 home('太白',18) 纔是真生的添加了額外的功能。可是每次寫這一句也是很麻煩。因此,Python給咱們提供了一個簡化機制,用一個很簡單的符號去代替這一句話。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') home('太白',18)
你看此時我調整了一下位置,你要是不把裝飾器放在上面,timer是找不到的。home函數若是想要加上裝飾器那麼你就在home函數上面加上@home,就等同於那句話 home = timer(home)。這麼作沒有什麼特殊意義,就是讓其更簡單化,好比你在影視片中見過野戰軍的做戰時因爲不方便說話,用一些簡單的手勢表明一些話語,就是這個意思。
至此標準版的裝飾器就是這個樣子:
def wrapper(func): def inner(*args,**kwargs): '''執行被裝飾函數以前的操做''' ret = func '''執行被裝飾函數以後的操做''' return ret return inner
這個就是標準的裝飾器,徹底符合代碼開放封閉原則。這幾行代碼必定要背過,會用。
此時咱們要利用這個裝飾器完成一個需求:簡單版模擬博客園登陸。 此時帶着學生們看一下博客園,說一下需求: 博客園登錄以後有幾個頁面,diary,comment,home,若是我要訪問這幾個頁面,必須驗證我是否已登陸。 若是已經成功登陸,那麼這幾個頁面我均可以無阻力訪問。若是沒有登陸,任何一個頁面都不能夠訪問,我必須先登陸,登陸成功以後,才能夠訪問這個頁面。咱們用成功執行函數模擬做爲成功訪問這個頁面,如今寫三個函數,寫一個裝飾器,實現上述功能。
def auth(): pass def diary(): print('歡迎訪問日記頁面') def comment(): print('歡迎訪問評論頁面') def home(): print('歡迎訪問博客園主頁') 答案: login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth def diary(): print('歡迎訪問日記頁面') @auth def comment(): print('歡迎訪問評論頁面') @auth def home(): print('歡迎訪問博客園主頁') diary() comment() home()
咱們看,裝飾器其實就是一個閉包函數,再說簡單點就是兩層的函數。那麼是函數,就應該具備函數傳參功能。
login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner
你看我上面的裝飾器,不要打開,他能夠不可在套一層:
def auth(x): def auth2(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth
舉例說明:抖音:綁定的是微信帳號密碼。 皮皮蝦:綁定的是qq的帳號密碼。 你如今要完成的就是你的裝飾器要分狀況去判斷帳號和密碼,不一樣的函數用的帳號和密碼來源不一樣。 可是你以前寫的裝飾器只能接受一個參數就是函數名,因此你寫一個能夠接受參數的裝飾器。
def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if 微信: username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif 'qq': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth2 def jitter(): print('記錄美好生活') @auth2 def pipefish(): print('期待你的內涵神評論')
解決方式:
def auth(x): def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if x == 'wechat': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif x == 'qq': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth2 @auth('wechat') def jitter(): print('記錄美好生活') @auth('qq') def pipefish(): print('期待你的內涵神評論')
@auth('wechat') :分兩步:
第一步先執行auth('wechat')函數,獲得返回值auth2
第二步@與auth2結合,造成裝飾器@auth2 而後在依次執行。
這樣就是帶參數的裝飾器,參數能夠傳入多個,通常帶參數的裝飾器在之後的工做中都是給你提供的, 你會用就行,可是本身也必定要會寫,面試常常會遇到。