Python學習之裝飾器與類的裝飾器詳解
通過學習裝飾器可以讓我們更好更靈活的使用函數,通過學會使用裝飾器還可以讓我們的代碼更加優雅。
在我們的實際工作中,很多場景都會用到裝飾器,比如記錄一些日志、或者屏蔽一些不太合法的程序執行從而使我們的代碼更加安全。
裝飾器
什麼是裝飾器?雖然對這個次感到陌生,但是完全不需要擔心。
首先,裝飾器也是一種函數;隻不過裝飾器可以接收 函數 作為參數來傳遞。
並且可以通過 return 可以返回一個函數,裝飾器通過接收一個函數,對它在裝飾器內部進行處理、調用,並返回一個新的函數,同時還可以動態增強傳入函數的功能。
裝飾器整個流程是這樣的:
- A函數是裝飾器,B函數是A函數傳入的參數。
- 將B函數在A函數中執行,在A函數中可以選擇執行或不執行,也可以對B函數的結果進行二次加工處理。
接下來我們看看 裝飾器長什麼樣子
def a(): def b(): print(helloworld) b() a() b()
a() 函數中書寫瞭一個 b() 函數,並在 a() 函數中調用 b() 函數。是不是非常類似在類中定義一個局部函數並調用的例子?其實裝飾器就是有些類似這樣的操作,隻不過被裝飾器調用的函數是通過 參數 的形式傳進去,並在 b() 函數中執行。
我們在定義完 a() 函數之後進行調用,可以正常處理。但是 b() 函數 是 a() 函數的局部函數 如果在外部調用會報錯。(如上文中的第十行,就會報錯)
裝飾器的定義
示例如下:
def out(func_args): # 裝飾器的第一層函數被稱為 外圍函數 , 'func_args' 為要處理的函數 def inter(*args, **kwargs): # 外圍函數 的函數體內定義的函數被稱為 內嵌函數 ;傳入的參數為要處理的 func_args 函數的參數 # 這裡我們並不知道 func_args 函數需要傳入進來的參數是什麼,所以目前寫傳入可變參數是比較合理的 return func_args(*args, **kwargs) # 在 內嵌函數 的函數體內調用 func_args 函數,並將可變參數傳入 # 其實這裡我們可以處理更多的邏輯; # 我們可以選擇執行或者不執行,甚至可以func_args 函數的執行結果進行二次處理 return inter # 書寫完 內嵌函數的業務之後,我們在 外圍函數體內返回 內嵌函數 # 需要註意的是,這裡是不執行的(因為沒有加括號),這是裝飾器的定義規則,是必不可少的 # 隻有外圍函數返回內嵌函數,才可以被之後的代碼執行;(因為所有的業務都在內嵌函數中,不返回就無法執行調用)
裝飾器的用法
在我們日常工作中,裝飾器的使用方法有兩種。
第一種:將被調用的函數直接作用為參數傳入裝飾器的外圍函數括弧內;示例如下:
def a(func): def b(*args, **kwargs): return func(*args, **kwargs) return b def c(name): print(name) a(c)('Neo') # >>> 執行結果如下: # >>> Neo
第二種:將裝飾器與被調用函數綁定在一起, @ 符號 + 裝飾器函數放在被調用函數的上一行,被調用的函數正常定義,隻需要直接調用被執行函數即可。示例如下:
def a(func): def b(*args, **kwargs): return func(*args, **kwargs) return b @a def c(name): print(name) c('Neo') # >>> 執行結果如下: # >>> Neo
最常用的裝飾器用法為第二種。
現在我們構建一個 檢查字符串類型的裝飾器,加深一下對裝飾器的理解。
def check_ok(func): def inner(*args, **kwargs): result = func(*args, **kwargs) if result == 'OK': return '傳入的參數數據為:\'%s\'' % result else: return '傳入的參數數據不為:\'OK\'' return inner @check_ok def test_str(data): return data result = test_str('OK') print(result) # >>> 執行結果如下: # >>> 傳入的參數數據為:'OK' result = test_str('NO') print(result) # >>> 執行結果如下: # >>> 傳入的參數數據不為:'OK'
以上就是一個裝飾器的簡單用法,後續的學習內容會接觸到更多的高級用法。
類中的裝飾器
類的裝飾器 – classmethod
classmethod 的功能:可以將類函數不經過實例化即可直接被調用
classmethod 的用法:示例如下
@classmethod def func(cls, ...): todo # >>> cls 替代普通類函數中的 self ; # >>> 變更為 cls ,表示當前的類 # >>> self 代表的是 實例化對象,所以隻有通過實例化後,才可以調用 # >>> cls 代表的是 類 ,所以即使不通過實例化,也可以調用 # ***************************************************** class Test(object): @classmethod def add(cls, a, b): return a + b print(Test.add(1, 3)) # >>> 執行結果如下: # >>> 4
演示案例:
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') @classmethod def work(cls): print('會抓老鼠') dragonLi = Cat('貍花貓') print(dragonLi.eat(), dragonLi.work()) # >>> 執行結果如下: # >>> 貍花貓 喜歡吃魚 # >>> 會抓老鼠
接下來我們不使用 類的實例化 ,直接使用 類 調用 eat() 函數 與 work() 函數
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') @classmethod def work(cls): print('會抓老鼠') dragonLi = Cat('貍花貓') Cat.eat() # >>> 執行結果如下: # >>> TypeError: Cat.eat() missing 1 required positional argument: 'self' # >>> 報錯缺少重要參數 'self' (沒有進行實例化的類,類無法直接調用類函數) Cat.work() # >>> 執行結果如下: # >>> 會抓老鼠 # >>> 綁定瞭 classmethod 裝飾器 的 work() 函數,即使沒有實例化,也可以直接被 類 調用
再嘗試一下看看 沒有裝飾器的 eat() 函數 與 使用瞭 classmethod 裝飾器 work() 之間可不可以互相調用
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') @classmethod def work(cls): print('會抓老鼠') cls.eat() # 在 classmethod 裝飾器的 work() 函數內 調用 eat() 函數 dragonLi = Cat('貍花貓') dragonLi.work() # >>> 執行結果如下: # >>> TypeError: Cat.eat() missing 1 required positional argument: 'self' # >>> 同樣報錯缺少重要參數 'self'
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') self.work() @classmethod def work(cls): print('會抓老鼠') dragonLi01 = Cat('貍花貓') dragonLi01.eat() # >>> 執行結果如下: # >>> 執行結果如下: # >>> 貍花貓 喜歡吃魚 # >>> 會抓老鼠
綜合以上兩個場景,我們得出以下結論:
- 在帶有 classmethod 裝飾器 的 函數 內,是無法調用普通的 帶有 self 的函數的
- 但是在普通的帶有 self 的類函數內,是可以調用帶有 classmethod 裝飾器 的 函數的
類的裝飾器 – staticmethod
staticmethod 的功能:可以將 類函數 不經過實例化而直接被調用,被該裝飾器調用的函數不需要傳入 self 、cls 參數,並且無法在該函數內調用其他 類函數 或 類變量
staticmethod 的用法:參考如下
@staticmethod def func(...): todo # >>> 函數內無需傳入 cls 或 self 參數 # ***************************************************** class Test(object): @staticmethod def add(a, b): return a + b print(Test.add(1, 3)) # >>> 執行結果如下: # >>> 4
接下來我們在上文的 Cat() 類基礎演示一下 staticmethod 裝飾器 (新建一個 color() 函數,使用 staticmethod 裝飾器 )
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') self.work() @classmethod def work(cls): print('會抓老鼠') @staticmethod def color(): print('黃棕色') dragonLi = Cat('貍花貓') print(dragonLi.eat(), dragonLi.color()) # >>> 執行結果如下: # >>> 貍花貓 喜歡吃魚 # >>> 會抓老鼠 # >>> 黃棕色 # >>> 從執行結果來看, staticmethod 裝飾器的 color() 函數可以被實例化後的對象 dragonLi 調用。 # >>> 那麼可以被 Cat() 類 直接調用麼?我們往下看 print(Cat.color()) # >>> 執行結果如下: # >>> 黃棕色 # >>> 可以看到,staticmethod 裝飾器構造的 color() 函數,即使沒有被實例化,依然可以直接被 類 調用
同樣的,也嘗試一下 staticmethod 裝飾器構造的 color() 函數 是否能夠在類函數中互相調用。
class Cat(object): def __init__(self, name): self.name = name def eat(self): print(self.name, '喜歡吃魚') self.work() self.color() @classmethod def work(cls): print('會抓老鼠') @staticmethod def color(): print('黃棕色') dragonLi = Cat('貍花貓') dragonLi.eat() # >>> 執行結果如下: # >>> 貍花貓 喜歡吃魚 # >>> 會抓老鼠 # >>> 黃棕色 # >>> 結合執行結果得出結論:staticmethod 裝飾器構造的 color() 函數 可以在 eat() 類函數中被調用
與帶有 classmethod 裝飾器 的 函數 一樣,staticmethod 裝飾器構造的 函數也是無法調用普通的 帶有 self 的函數的,這裡就不再書寫演示代碼進行演示瞭。(staticmethod 裝飾器構造的 函數也是無法調用普通的 帶有 self 的函數會報錯 : NameError: name 'self' is not defined )
類的裝飾器 – property
property 的功能:可以將類函數的執行免去小括號,類似於直接調用類的變量(屬性)
staticmethod 的用法:參考如下
@property def func(self): todo # >>> 不能傳入參數,無重要函數說明 # *************************示例如下************************* class Test(object): def __init__(self, name): self.name = name @property def call_name(self): return 'Hello {}'.format(self.name) test = Test('Neo') result = test.call_name # 不需要添加 小括號 即可調用 call_name 函數; # 關於 staticmethod 不可傳入參數,其實並不是不可以傳入參數,而是傳入的方法比較另類,我們繼續往下看 print(result) # >>> 執行結果如下: # >>> Hello Neo
重新創建一個 Dog 類 ,然後我們繼續演示。
class Dog(object): def __init__(self, name): self.__name = name @property def type(self): if self.__name in ['哈士奇', '薩摩耶', '阿拉斯基']: return self.__name, '是雪橇犬,\'雪橇三傻\'之一' elif self.__name in ['吉娃娃', '博美犬', '約克夏']: return self.__name, '是小型犬' else: return self.__name, '我暫時不知道這是什麼犬種,也許它是\'泰日天\'的親戚' dog = Dog(name='哈士奇') print(dog.type) # 這裡我們並不需要 dog.type + () 小括號,即可調用 type() 函數 # >>> 執行結果如下: # >>> ('哈士奇', "是雪橇犬,'雪橇三傻'之一") # >>> 這裡我們看到 當 Dog 類 實例化 dog 變量之後,我們傳入的 '哈士奇' 參數是不可更改的,如果我們嘗試利用賦值的方式修改傳入的參數呢? dog = Dog(name='哈士奇') dog.type = '約克夏' print(dog.type) # >>> 執行結果如下: # >>> AttributeError: can't set attribute # >>> 報錯:屬性錯誤,不可以設置這個屬性 # >>> 其實,property 裝飾器綁定的函數的參數並不是不可以更改,隻是更改的方式比較特殊,並不是不能通過賦值的形式傳入參數,我們繼續往下看。
首先,我們已經使用瞭 @property 綁定瞭我們的 type 函數,這是一個返回值的方法。 所以我們要如何給 type() 函數賦值呢?其實很簡單,我們可以通過 @type 對應上 type() 函數,在它的函數內部有一個函數 setter ;然後再定義一個 type 函數,在這個新定義的 type() 函數內定義一個值 value (可以是任意的名字,但這裡需要註意,隻能定義一個值)。然後再通過設置一個 self.__name = value ,如此就可以達到修改傳入參數的目的。廢話不多說瞭,看下方的示例:
class Dog(object): def __init__(self, name): self.__name = name @property def type(self): if self.__name in ['哈士奇', '薩摩耶', '阿拉斯基']: return self.__name, '是雪橇犬,\'雪橇三傻\'之一' elif self.__name in ['吉娃娃', '博美犬', '約克夏']: return self.__name, '是小型犬' else: return self.__name, '我暫時不知道這是什麼犬種,也許它是\'泰日天\'的親戚' @type.setter def type(self, value): self.__name = value dog = Dog(name='哈士奇') dog.type = '約克夏' print(dog.type) # >>> 執行結果如下: # >>> ('約克夏', '是小型犬')
附:使用最廣泛的裝飾器為 classmethod
以上就是Python學習之裝飾器與類的裝飾器詳解的詳細內容,更多關於Python裝飾器的資料請關註WalkonNet其它相關文章!