Python學習之裝飾器與類的裝飾器詳解
通過學習裝飾器可以讓我們更好更靈活的使用函數(shù),通過學會使用裝飾器還可以讓我們的代碼更加優(yōu)雅。
在我們的實際工作中,很多場景都會用到裝飾器,比如記錄一些日志、或者屏蔽一些不太合法的程序執(zhí)行從而使我們的代碼更加安全。
裝飾器
什么是裝飾器?雖然對這個次感到陌生,但是完全不需要擔心。
首先,裝飾器也是一種函數(shù);只不過裝飾器可以接收 函數(shù) 作為參數(shù)來傳遞。
并且可以通過 return 可以返回一個函數(shù),裝飾器通過接收一個函數(shù),對它在裝飾器內(nèi)部進行處理、調(diào)用,并返回一個新的函數(shù),同時還可以動態(tài)增強傳入函數(shù)的功能。
裝飾器整個流程是這樣的:
- A函數(shù)是裝飾器,B函數(shù)是A函數(shù)傳入的參數(shù)。
- 將B函數(shù)在A函數(shù)中執(zhí)行,在A函數(shù)中可以選擇執(zhí)行或不執(zhí)行,也可以對B函數(shù)的結(jié)果進行二次加工處理。
接下來我們看看 裝飾器長什么樣子
def a():
def b():
print(helloworld)
b()
a()
b()
a() 函數(shù)中書寫了一個 b() 函數(shù),并在 a() 函數(shù)中調(diào)用 b() 函數(shù)。是不是非常類似在類中定義一個局部函數(shù)并調(diào)用的例子?其實裝飾器就是有些類似這樣的操作,只不過被裝飾器調(diào)用的函數(shù)是通過 參數(shù) 的形式傳進去,并在 b() 函數(shù)中執(zhí)行。
我們在定義完 a() 函數(shù)之后進行調(diào)用,可以正常處理。但是 b() 函數(shù) 是 a() 函數(shù)的局部函數(shù) 如果在外部調(diào)用會報錯。(如上文中的第十行,就會報錯)
裝飾器的定義
示例如下:
def out(func_args): # 裝飾器的第一層函數(shù)被稱為 外圍函數(shù) , 'func_args' 為要處理的函數(shù)
def inter(*args, **kwargs): # 外圍函數(shù) 的函數(shù)體內(nèi)定義的函數(shù)被稱為 內(nèi)嵌函數(shù) ;傳入的參數(shù)為要處理的 func_args 函數(shù)的參數(shù)
# 這里我們并不知道 func_args 函數(shù)需要傳入進來的參數(shù)是什么,所以目前寫傳入可變參數(shù)是比較合理的
return func_args(*args, **kwargs) # 在 內(nèi)嵌函數(shù) 的函數(shù)體內(nèi)調(diào)用 func_args 函數(shù),并將可變參數(shù)傳入
# 其實這里我們可以處理更多的邏輯;
# 我們可以選擇執(zhí)行或者不執(zhí)行,甚至可以func_args 函數(shù)的執(zhí)行結(jié)果進行二次處理
return inter # 書寫完 內(nèi)嵌函數(shù)的業(yè)務之后,我們在 外圍函數(shù)體內(nèi)返回 內(nèi)嵌函數(shù)
# 需要注意的是,這里是不執(zhí)行的(因為沒有加括號),這是裝飾器的定義規(guī)則,是必不可少的
# 只有外圍函數(shù)返回內(nèi)嵌函數(shù),才可以被之后的代碼執(zhí)行;(因為所有的業(yè)務都在內(nèi)嵌函數(shù)中,不返回就無法執(zhí)行調(diào)用)
裝飾器的用法
在我們?nèi)粘9ぷ髦?,裝飾器的使用方法有兩種。
第一種:將被調(diào)用的函數(shù)直接作用為參數(shù)傳入裝飾器的外圍函數(shù)括弧內(nèi);示例如下:
def a(func):
def b(*args, **kwargs):
return func(*args, **kwargs)
return b
def c(name):
print(name)
a(c)('Neo')
# >>> 執(zhí)行結(jié)果如下:
# >>> Neo
第二種:將裝飾器與被調(diào)用函數(shù)綁定在一起, @ 符號 + 裝飾器函數(shù)放在被調(diào)用函數(shù)的上一行,被調(diào)用的函數(shù)正常定義,只需要直接調(diào)用被執(zhí)行函數(shù)即可。示例如下:
def a(func):
def b(*args, **kwargs):
return func(*args, **kwargs)
return b
@a
def c(name):
print(name)
c('Neo')
# >>> 執(zhí)行結(jié)果如下:
# >>> Neo
最常用的裝飾器用法為第二種。
現(xiàn)在我們構(gòu)建一個 檢查字符串類型的裝飾器,加深一下對裝飾器的理解。
def check_ok(func):
def inner(*args, **kwargs):
result = func(*args, **kwargs)
if result == 'OK':
return '傳入的參數(shù)數(shù)據(jù)為:\'%s\'' % result
else:
return '傳入的參數(shù)數(shù)據(jù)不為:\'OK\''
return inner
@check_ok
def test_str(data):
return data
result = test_str('OK')
print(result)
# >>> 執(zhí)行結(jié)果如下:
# >>> 傳入的參數(shù)數(shù)據(jù)為:'OK'
result = test_str('NO')
print(result)
# >>> 執(zhí)行結(jié)果如下:
# >>> 傳入的參數(shù)數(shù)據(jù)不為:'OK'
以上就是一個裝飾器的簡單用法,后續(xù)的學習內(nèi)容會接觸到更多的高級用法。
類中的裝飾器
類的裝飾器 - classmethod
classmethod 的功能:可以將類函數(shù)不經(jīng)過實例化即可直接被調(diào)用
classmethod 的用法:示例如下
@classmethod
def func(cls, ...):
todo
# >>> cls 替代普通類函數(shù)中的 self ;
# >>> 變更為 cls ,表示當前的類
# >>> self 代表的是 實例化對象,所以只有通過實例化后,才可以調(diào)用
# >>> cls 代表的是 類 ,所以即使不通過實例化,也可以調(diào)用
# *****************************************************
class Test(object):
@classmethod
def add(cls, a, b):
return a + b
print(Test.add(1, 3))
# >>> 執(zhí)行結(jié)果如下:
# >>> 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())
# >>> 執(zhí)行結(jié)果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
接下來我們不使用 類的實例化 ,直接使用 類 調(diào)用 eat() 函數(shù) 與 work() 函數(shù)
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()
# >>> 執(zhí)行結(jié)果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 報錯缺少重要參數(shù) 'self' (沒有進行實例化的類,類無法直接調(diào)用類函數(shù))
Cat.work()
# >>> 執(zhí)行結(jié)果如下:
# >>> 會抓老鼠
# >>> 綁定了 classmethod 裝飾器 的 work() 函數(shù),即使沒有實例化,也可以直接被 類 調(diào)用
再嘗試一下看看 沒有裝飾器的 eat() 函數(shù) 與 使用了 classmethod 裝飾器 work() 之間可不可以互相調(diào)用
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() 函數(shù)內(nèi) 調(diào)用 eat() 函數(shù)
dragonLi = Cat('貍花貓')
dragonLi.work()
# >>> 執(zhí)行結(jié)果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 同樣報錯缺少重要參數(shù) '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()
# >>> 執(zhí)行結(jié)果如下:
# >>> 執(zhí)行結(jié)果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
綜合以上兩個場景,我們得出以下結(jié)論:
- 在帶有 classmethod 裝飾器 的 函數(shù) 內(nèi),是無法調(diào)用普通的 帶有 self 的函數(shù)的
- 但是在普通的帶有 self 的類函數(shù)內(nèi),是可以調(diào)用帶有 classmethod 裝飾器 的 函數(shù)的
類的裝飾器 - staticmethod
staticmethod 的功能:可以將 類函數(shù) 不經(jīng)過實例化而直接被調(diào)用,被該裝飾器調(diào)用的函數(shù)不需要傳入 self 、cls 參數(shù),并且無法在該函數(shù)內(nèi)調(diào)用其他 類函數(shù) 或 類變量
staticmethod 的用法:參考如下
@staticmethod
def func(...):
todo
# >>> 函數(shù)內(nèi)無需傳入 cls 或 self 參數(shù)
# *****************************************************
class Test(object):
@staticmethod
def add(a, b):
return a + b
print(Test.add(1, 3))
# >>> 執(zhí)行結(jié)果如下:
# >>> 4
接下來我們在上文的 Cat() 類基礎演示一下 staticmethod 裝飾器 (新建一個 color() 函數(shù),使用 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())
# >>> 執(zhí)行結(jié)果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
# >>> 黃棕色
# >>> 從執(zhí)行結(jié)果來看, staticmethod 裝飾器的 color() 函數(shù)可以被實例化后的對象 dragonLi 調(diào)用。
# >>> 那么可以被 Cat() 類 直接調(diào)用么?我們往下看
print(Cat.color())
# >>> 執(zhí)行結(jié)果如下:
# >>> 黃棕色
# >>> 可以看到,staticmethod 裝飾器構(gòu)造的 color() 函數(shù),即使沒有被實例化,依然可以直接被 類 調(diào)用
同樣的,也嘗試一下 staticmethod 裝飾器構(gòu)造的 color() 函數(shù) 是否能夠在類函數(shù)中互相調(diào)用。
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()
# >>> 執(zhí)行結(jié)果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
# >>> 黃棕色
# >>> 結(jié)合執(zhí)行結(jié)果得出結(jié)論:staticmethod 裝飾器構(gòu)造的 color() 函數(shù) 可以在 eat() 類函數(shù)中被調(diào)用
與帶有 classmethod 裝飾器 的 函數(shù) 一樣,staticmethod 裝飾器構(gòu)造的 函數(shù)也是無法調(diào)用普通的 帶有 self 的函數(shù)的,這里就不再書寫演示代碼進行演示了。(staticmethod 裝飾器構(gòu)造的 函數(shù)也是無法調(diào)用普通的 帶有 self 的函數(shù)會報錯 : NameError: name 'self' is not defined )
類的裝飾器 - property
property 的功能:可以將類函數(shù)的執(zhí)行免去小括號,類似于直接調(diào)用類的變量(屬性)
staticmethod 的用法:參考如下
@property
def func(self):
todo
# >>> 不能傳入?yún)?shù),無重要函數(shù)說明
# *************************示例如下*************************
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 # 不需要添加 小括號 即可調(diào)用 call_name 函數(shù);
# 關于 staticmethod 不可傳入?yún)?shù),其實并不是不可以傳入?yún)?shù),而是傳入的方法比較另類,我們繼續(xù)往下看
print(result)
# >>> 執(zhí)行結(jié)果如下:
# >>> Hello Neo
重新創(chuàng)建一個 Dog 類 ,然后我們繼續(xù)演示。
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 + () 小括號,即可調(diào)用 type() 函數(shù)
# >>> 執(zhí)行結(jié)果如下:
# >>> ('哈士奇', "是雪橇犬,'雪橇三傻'之一")
# >>> 這里我們看到 當 Dog 類 實例化 dog 變量之后,我們傳入的 '哈士奇' 參數(shù)是不可更改的,如果我們嘗試利用賦值的方式修改傳入的參數(shù)呢?
dog = Dog(name='哈士奇')
dog.type = '約克夏'
print(dog.type)
# >>> 執(zhí)行結(jié)果如下:
# >>> AttributeError: can't set attribute
# >>> 報錯:屬性錯誤,不可以設置這個屬性
# >>> 其實,property 裝飾器綁定的函數(shù)的參數(shù)并不是不可以更改,只是更改的方式比較特殊,并不是不能通過賦值的形式傳入?yún)?shù),我們繼續(xù)往下看。
首先,我們已經(jīng)使用了 @property 綁定了我們的 type 函數(shù),這是一個返回值的方法。 所以我們要如何給 type() 函數(shù)賦值呢?其實很簡單,我們可以通過 @type 對應上 type() 函數(shù),在它的函數(shù)內(nèi)部有一個函數(shù) setter ;然后再定義一個 type 函數(shù),在這個新定義的 type() 函數(shù)內(nèi)定義一個值 value (可以是任意的名字,但這里需要注意,只能定義一個值)。然后再通過設置一個 self.__name = value ,如此就可以達到修改傳入?yún)?shù)的目的。廢話不多說了,看下方的示例:
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)
# >>> 執(zhí)行結(jié)果如下:
# >>> ('約克夏', '是小型犬')
附:使用最廣泛的裝飾器為 classmethod
以上就是Python學習之裝飾器與類的裝飾器詳解的詳細內(nèi)容,更多關于Python裝飾器的資料請關注腳本之家其它相關文章!
相關文章
Scrapy模擬登錄趕集網(wǎng)的實現(xiàn)代碼
這篇文章主要介紹了Scrapy模擬登錄趕集網(wǎng)的實現(xiàn)代碼,本文通過代碼圖文相結(jié)合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
VScode查看python f.write()的文件亂碼問題及解決方法
這篇文章主要介紹了VScode查看python f.write()的文件亂碼問題及解決方法,本文通過圖文并茂的形式給大家分享解決方法,需要的朋友可以參考下2023-02-02
深入解析Python的Tornado框架中內(nèi)置的模板引擎
模板引擎是Web開發(fā)框架中負責前端展示的關鍵,這里我們就來以實例深入解析Python的Tornado框架中內(nèi)置的模板引擎,來學習如何編寫Tonardo的模板.2016-07-07
python輕松實現(xiàn)代碼編碼格式轉(zhuǎn)換
由于某些原因,需要將代碼從A機房遷移到B機房,這兩個之間不能互相訪問,但是歷史原因?qū)е翧機房的代碼全是utf8編碼的,B機房要求是GBK編碼,看看這個怎么解決。雖然很簡單,但是還是要推薦給大家,需要的小伙伴參考下吧。2015-03-03
python使用裝飾器和線程限制函數(shù)執(zhí)行時間的方法
這篇文章主要介紹了python使用裝飾器和線程限制函數(shù)執(zhí)行時間的方法,主要涉及timelimited函數(shù)的使用技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04

