1. 程式人生 > >測開之函式進階· 第7篇《裝飾器裝飾類,通用裝飾器,有啥區別呢?》

測開之函式進階· 第7篇《裝飾器裝飾類,通用裝飾器,有啥區別呢?》

### 堅持原創輸出,點選藍字關注我吧 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105123255.png) 作者:清菡 部落格:oschina、雲+社群、知乎等各大平臺都有。 > 由於微信公眾號推送改為了資訊流的形式,防止走丟,請給加個星標 ⭐,你就可以第一時間接收到本公眾號的推送! # 目錄 - 一、什麼是裝飾器 - 1.開放封閉原則(面向物件原則的核心) - 2.裝飾器的作用 - 二、實現一個裝飾器 - 1.不帶引數的裝飾器 - 2.裝飾器的原理 - 3.組裝方便,拆卸也方便 - 4.帶引數的裝飾器 - 三、通用裝飾器 - 四、裝飾器裝飾類 - 1.不帶引數的 - 2.帶引數的 - 五、裝飾器的應用場景 - 六、補充 - 1.*號的作用 - 2.裝飾器裝飾類和裝飾函式的不同點 ## 一、什麼是裝飾器 ### 1.開放封閉原則(面向物件原則的核心) **對已經實現的功能(專案已經上線了),在這個基礎上增加新功能,也可以在它的基礎上進行拓展,這個就是開放。如果你要去再修改它內部的程式碼,這個時候是不允許的,對內部的修改是封閉的。** 就是你實現的功能可以拓展,但是你不要去修改它內部的程式碼。 比如`index()`是介面,返回是“這個是網站的首頁”,只要呼叫這個介面就會返回一個“這個是網站的首頁。” 突然有個需求,在進入網站之前需要先登入校驗一下。`這個時候需要拓展,如何拓展?就需要用裝飾器了。` ```PYTHON def index(): print("這個是網站的首頁") ``` 這個已經實現的介面,不能去修改的。 ### 2.裝飾器的作用 **裝飾器可在不更改這個函式裡面任何程式碼的基礎上,給它新增新的功能。** ## 二、實現一個裝飾器 ### 1.不帶引數的裝飾器 裝飾器其實就是一種閉包的應用。要使用裝飾器,可以先定義個閉包函式。把登入校驗的功能寫在了閉包函式的內部。 把閉包函式當成裝飾器來用的話,外面接收的引數需要傳一個函式,你要裝飾哪個函式,你就傳哪個函式。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210104191754.png) ```PYTHON # 開放封閉原則 def login(func): def fun(): # 簡單的校驗 username = 'python01' password = 'qinghan' user = input("請輸入賬號:") pw = input("請輸入密碼:") # 判斷下賬號密碼對不對 if username == user and pw == password: func() # 登入得賬號密碼都正確的情況下,呼叫這個函式 else: print("賬號或密碼錯誤") return fun @login # 艾特一下這個裝飾器 def index(): print("這個是網站的首頁") index() ``` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210104194731.png) ### 2.裝飾器的原理 **將被裝飾的函式當作一個引數傳到裝飾器中,並且讓被裝飾的函式名指向裝飾器內部的函式,在裝飾器的內部函式中用接收到的引數再呼叫被裝飾的函式。** `@login`是 Python 中的一個語法糖。它的作用是:`index=login(index)`。傳入`index`,然後被`index`接收。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210104200649.png) `如何做到通過func()呼叫原函式?` ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105081426.png) `@login`等於`index=login(index)`。 自動將`index`當作引數傳入`login`這個函式裡面,去執行`login(func)`這個函式,檢測到這個`fun()`函式,將這段程式碼: ```python def fun(): # 簡單的校驗 username = 'python01' password = 'qinghan' user = input("請輸入賬號:") pw = input("請輸入密碼:") # 判斷下賬號密碼對不對 if username == user and pw == password: func() # 登入得賬號密碼都正確的情況下,呼叫這個函式 else: print("賬號或密碼錯誤") ``` 直接跳過。 `return fun`直接將結果返回出來。結果返回出來又給`index()`接收,呼叫`index()`的時候實際上是呼叫`fun()`函式。 執行`fun()`函式裡面的程式碼。通過`func()`呼叫原函式,怎麼做到的? `fun()`函式是放在`index.__closure__`這個屬性裡面。 然後在下面呼叫`func()`的時候,就是去`index.__closure__`這個屬性裡面找到對應儲存的那塊程式碼。 儲存的程式碼就是這個: ```python def index(): print("這個是網站的首頁") ``` 以上,就是裝飾器裝飾的流程。 ### 3.組裝方便,拆卸也方便 我想改成不用登入也可以訪問,直接去掉`@login`這個裝飾器就可以了。 ```PytHON # 開放封閉原則 def login(func): def fun(): # 簡單的校驗 username = 'python01' password = 'qinghan' user = input("請輸入賬號:") pw = input("請輸入密碼:") # 判斷下賬號密碼對不對 if username == user and pw == password: func() # 登入得賬號密碼都正確的情況下,呼叫這個函式 else: print("賬號或密碼錯誤") return fun # @login # 艾特一下這個裝飾器 def index(): print("這個是網站的首頁") # index.__closure__ index() ``` 這樣操作,不會對原來有什麼影響。 ### 4.帶引數的裝飾器 實現兩個數相加後,又有新的需求,需要可以相乘、相除。 ```PYTHON def add(func): def fun(a, b): print("相乘", a * b) print("相除", a / b) func(a, b) return fun @add def add_num(a, b): # 列印兩個數相加 print("相加:", a + b) add_num(11, 22) ``` ![引數傳遞的過程](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105093213.png) ## 三、通用裝飾器 如果同一個裝飾器既要裝飾有引數的函式,又要裝飾無引數的函式。 那麼我們在傳參的時候就設定成不定長引數,這樣不管被裝飾的函式有沒有引數都能用。 ```PYTHON # 通用裝飾器 def add(func): def fun(*args, **kwargs): print("裝飾器的功能程式碼:登入") func(*args,**kwargs) return fun @add def index(): print("這個是網站的首頁") @add def good_list(num): print("這個是商品列表第{}頁".format(num)) index() print("------------") good_list(9) ``` ## 四、裝飾器裝飾類 ### 1.不帶引數的 ```python #裝飾器裝飾類 def add(func): def fun(*args, **kwargs): print("裝飾器的功能程式碼:登入") return func(*args,**kwargs) return fun @add # MyClass=add(MyClass) class MyClass: def __init__(self): pass m = MyClass() print("m的值:",m) ``` ![裝飾器裝飾類的原理](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105112711.png) 把類當作一個引數傳到裝飾器裡面。`return fun`返回的是`fun`,`MyClass`接收到的是`fun`。 `MyClass()`呼叫的是`fun`。 執行程式碼: ```python def fun(*args, **kwargs): print("裝飾器的功能程式碼:登入") return func(*args,**kwargs) ``` 這裡面的功能。 先執行裝飾器的功能,`return func(*args,**kwargs)`,`func()`來自`def add(func)`。 呼叫`MyClass`這個類,`return func(*args,**kwargs)`建立了個物件,`MyClass()`呼叫完了接收,m 就能接收這個物件了。 這個就是裝飾器裝飾類的一個原理。 ### 2.帶引數的 ```python #裝飾器裝飾類 def add(func): def fun(*args, **kwargs): print("裝飾器的功能程式碼:登入") return func(*args,**kwargs) return fun @add # MyClass=add(MyClass) class MyClass: def __init__(self,name,age): self.name=name self.age=age m = MyClass("qinghan","18") print("m的值:",m) ``` 這裡用的是不定行引數,所以不管你裝飾的類是有引數的還是沒引數的,都可以。 ## 五、裝飾器的應用場景 1.登入校驗。(在裝飾器裡面判斷下你有沒有登入) 2.函式執行時間統計。 3.執行函式之前做準備工作。 4.執行函式後清理功能。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105090744.png) ## 六、補充 ### 1.*號的作用 `*`是進行拆包作用的。把每個元素拿出來,當作引數進行傳遞。 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105102508.png) ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105102534.png) 一個`*`是對元組形式的位置引數進行拆包,兩個`**`對關鍵字引數進行拆包。 ### 2.裝飾器裝飾類和裝飾函式的不同點 ![](https://gitee.com/qinghanstudy/qinghan/raw/master/img/20210105113021.png) 類需要把物件返回出來。 --- 公眾號**清菡軟體測試**首發,更多原創文章:清菡軟體測試 116+原創文章,歡迎關注、交流,禁止第三方擅自