大家好诺擅,我是劍南。近期有不少人咨詢有關裝飾器的知識毙石,很多人對這一塊了解甚少,因此我便打算出兩期文章來專門寫裝飾器的知識颓遏,希望可以對大家?guī)韼椭?/p>
關于裝飾器介紹
python中裝飾器是指任何可以修改函數(shù)或類的可調用對象徐矩。它們允許一些類似于類似于其他語言的附加功能,例如將方法聲明為類或者是靜態(tài)方法叁幢。
類方法是在類是在類滤灯,而不是在特定實例調用的方法。靜態(tài)方法類似于類方法,但將應用于類的所有實例鳞骤,而不僅僅是特定的實例窒百。在python中,實例方法是處理OOP的傳統(tǒng)方法豫尽。
當一個函數(shù)被調用時篙梢,它被傳遞給裝飾器,裝飾器返回一個被修改過的函數(shù)/類美旧,這些修改過的對象通常包含調用最初的調用對象渤滞。
注意:裝飾器可以與函數(shù)或者方法一起使用,但通常都會簡稱它們?yōu)椤昂瘮?shù)”榴嗅,這使得描述更加簡潔妄呕。方法將在討論類的時候時使用。
簡單回顧函數(shù)
因為在處理裝飾器時嗽测,理解函數(shù)是如何工作的是非常重要的酝润,所以我們會快速瀏覽一下它們驻呐。首先习柠,我們需要記住蛉艾,python中一切都是對象,包括函數(shù)晤愧。
在python中使用def關鍵字并給出命名來創(chuàng)建函數(shù)蠢护。輸入參數(shù)是可選的,以下是一些基本參考功能养涮。
def func_foo():
pass
實現(xiàn)方法
(1)函數(shù)可以有多個名稱,其實也可以這樣理解眉抬,除了函數(shù)名本身贯吓,還可以將一個函數(shù)分配給其他變量名,每個變量名與基礎函數(shù)具有相同的功能蜀变。
def first_func1(val):
print(val)
new_name = first_func1
first_func1("hello world")
print('*'*50)
new_name('hello world too')
運行結果悄谐,如下所示:
hello world
**************************************************
hello world too
(2)創(chuàng)建函數(shù)時,參數(shù)是可以是其他函數(shù)库北,可以是自定義函數(shù)爬舰,也可以是內置函數(shù),例如map()和filter()寒瓦,使用這個特性來完成工作情屹。
具體代碼,如下所示:
def mult(x, y):
return x * y
def div(x, y):
return x/y
def math(func, x ,y):
result = func(x, y)
return result
val1 = math(mult, 4, 2)
print(val1)
print("*" * 50)
val2 = math(div, 4, 2)
print(val2)
運行結果杂腰,如下所示:
8
**************************************************
2.0
(3)像循環(huán)一樣垃你,函數(shù)也可以做嵌套操作。
具體代碼,如下所示:
def person(name):
def greeting():
return 'would you like some milk'
greet = greeting() + name + '?'
return greet
print(person('Sir Galahad'))
運行結果惜颇,如下所示:
would you like some milkSir Galahad?
(4)函數(shù)可以用作其他函數(shù)的參數(shù)皆刺。這是因為函數(shù)參數(shù)實際上是對對像的引用,由于函數(shù)是對象凌摄,因此函數(shù)(實際上是對函數(shù)對象的引用)可以用作參數(shù)羡蛾。
具體代碼,如下所示:
def greeting(name):
return "'allo " + name
def call_me(func):
nickname = 'mate'
return func(nickname)
print(call_me(greeting))
運行結果锨亏,如下所示:
'allo mate
同樣痴怨,函數(shù)可以返回函數(shù),這是因為函數(shù)的返回值是對對象的引用屯伞。
具體代碼腿箩,如下所示:
def func_creator():
def return_saying():
return 'Blessed are the cheese makers'
return return_saying
statement = func_creator()
print(statement())
運行結果,如下所示:
Blessed are the cheese makers
(6)嵌套函數(shù)可以訪問其父函數(shù)的作用域劣摇,這也稱為閉包珠移。我們必須認識到,這種訪問是只讀的末融,嵌套函數(shù)不能寫出钧惧,或將變量賦值給外部作用域。
在實際情況下勾习,這與哈函數(shù)變量分配參數(shù)沒有什么不同浓瞪,輸入參數(shù)只是將其傳遞給另一個封閉函數(shù),而不是變量巧婶。
具體代碼乾颁,如下所示:
def func_creator2(name):
def greeting():
return 'welcome ' + name
return greeting
greet = func_creator2('Jack')
print(greet())
運行結果,如下所示:
welcome Jack
工作原理
函數(shù)及其面向對象的兄弟——方法艺栈,是許多編程語言中必不可少的元素英岭,它們允許代碼的復用,因為可以從代碼中的不同位置多次調用函數(shù)湿右。如果語言支持诅妹,甚至可以從不同的程序中調用它們,例如導入python毅人。
函數(shù)還允許抽象工作吭狡,在某種程度上,函數(shù)類似于黑盒丈莺。開發(fā)人員只需要知道為函數(shù)提供什么數(shù)據划煮、函數(shù)如何處理這些數(shù)據,以及是否返回值场刑。只要結果一致般此,就不需要知道函數(shù)的實現(xiàn)過程蚪战,內部代碼你是不知道的。
編寫一個沒有函數(shù)的程序是可行的铐懊,但是它要求對整個程序進行串行處理邀桑。所有需要重復的功能代碼必須每次都需要復制粘貼,這也是為什么最早的高級語言也包含了子程序科乎,子程序允許程序員跳出主程序的的邏輯來處理一些數(shù)據壁畸,然后返回到主程序。
在此之前茅茂,必須使用特殊的調用序列來實現(xiàn)子程序捏萍,以便將返回地址存儲到主程序中。
裝飾器簡介
有了以上的基礎空闲,接下來就可以價紹裝飾器了令杈。裝飾器是將一個函數(shù)包裝在另一個函數(shù)中,該函數(shù)以某種方式修改原始函數(shù)碴倾,例如添加功能逗噩、修改參數(shù)或結果,等待跌榔。
實現(xiàn)方法
(1)首先要定義裝飾器函數(shù)
# 定義裝飾器函數(shù)
def fun_decorator(some_funct):
def wrapper():
print('這是一個裝飾器')
for i in range(10):
print(i)
print('裝飾器運行完畢异雁,返回原始的調度方法')
print(some_funct())
return wrapper
(2)定義主函數(shù)
# 定義主函數(shù)
def main_fun():
text = '我是主程序'
return text
(3)將主函數(shù)作為一個變量傳遞給裝飾器,并把這個裝飾器賦值給主函數(shù)
main_fun = fun_decorator(main_fun)
(4)調用主函數(shù)
main_fun()
(5)將上面的代碼進行整合僧须,如下所示:
# 定義裝飾器函數(shù)
def fun_decorator(some_funct):
def wrapper():
print('這是一個裝飾器')
for i in range(10):
print(i)
print('裝飾器運行完畢纲刀,返回原始的調度方法')
print(some_funct())
return wrapper
# 定義主函數(shù)
def main_fun():
text = '我是主程序'
return text
main_fun = fun_decorator(main_fun)
main_fun()
(6)運行代碼,結果如下圖所示:
(7)我們可以使用語法糖(@符號)來注釋主函數(shù)是由fun_decorator修改的担平,這樣可以消除main_fun = fun_decorator(main_fun)示绊,具體代碼,如下所示:
# 定義裝飾器函數(shù)
def fun_decorator(some_funct):
def wrapper():
print('這是一個裝飾器')
for i in range(10):
print(i)
print('裝飾器運行完畢暂论,返回原始的調度方法')
print(some_funct())
return wrapper
@fun_decorator
def main_fun():
text = '我是主程序'
return text
main_fun()
(8)修改代碼后耻台,運行結果一致,如下圖所示:
工作原理
當一個帶有裝飾器的函數(shù)被調用空另,裝飾器函數(shù)會捕捉到這個調用,然后裝飾器函數(shù)會執(zhí)行它的工作蹋砚。在它完成之后扼菠,移交給原始函數(shù),從而完成任務坝咐。
語法糖是編程語言中的一種特殊語法循榆,旨在通過使代碼更易于讀或寫,令程序員的工作更輕松墨坚。語法糖表達式是通過查看糖丟失時代碼功能是否會一起丟失來識別的秧饮。
使用函數(shù)裝飾器
函數(shù)裝飾器顯然適用于函數(shù)映挂。@fun_decorator裝飾器行放在函數(shù)定義之前的行上。語法糖接收一個函數(shù)盗尸,并通過另一個函數(shù)自動運行其結果柑船。在處理結束時,原始函數(shù)調用的名稱應用于最終結果泼各。對于系統(tǒng)來說鞍时,它看起來像直接提供結果的原始函數(shù)調用。(但扣蜻,其實并不是)下面是裝飾器的演示逆巍。
@fun_decorator
def my_function():
pass
當python解釋器到達此代碼塊時,將處理my_function()并將結果傳遞給@fun_decorator所指向的函數(shù)莽使。裝飾器函數(shù)將被處理锐极,結果替換為原來的my_function()結果。從本質上說芳肌,裝飾器劫持了函數(shù)的調用灵再,修改原始結果,并將修改結果替換為原始函數(shù)提供的結果庇勃。
修改裝飾器代碼檬嘀,可以采用管理或增加原始調用的形式。一旦一個函數(shù)完成它的工作责嚷,裝飾器就接管并對原始結果進行處理鸳兽,返回修改后的代碼。
實現(xiàn)方法
(1)我們必須要明確裝飾器要做什么罕拂。對于下面的例子揍异,裝飾器函數(shù)將查看傳遞給函數(shù)的參數(shù),并檢查傳遞的值是否是整數(shù)爆班。
(2)編寫裝飾器函數(shù)
def arg_check(func):
def wrapper(num):
if type(num) != int:
raise TypeError('參數(shù)不是一個整數(shù)')
elif num <= 0:
raise ValueError('參數(shù)不是一個正數(shù)')
else:
return func(num)
return wrapper
(3)編寫需要修飾的函數(shù)
@arg_check
def circle_measures(radius):
circumference = 2 * pi *radius
area = pi * radius * radius
diameter = 2 * radius
return (circumference, area, diameter)
(4)調用并打印有關圓的值
circumference, area, diameter = circle_measures(6)
print('circumference is ', circumference, '\n', 'area is ', area, '\n', 'diameter is ', diameter)
注意:在實際情況下衷掷,這與為函數(shù)變量分配參數(shù)沒有什么不同。輸入參數(shù)只是將其傳遞給另一個封閉函數(shù)柿菩,而不是變量戚嗅。
例如,上面的代碼circle_measures(6)里面的6是傳遞給裝飾器中的wrapper()函數(shù)枢舶。
使用類裝飾器
從python的2.6版本開始懦胞,裝飾器就被用于處理類。在這種情況下凉泄,裝飾器不僅可以應用于函數(shù)躏尉,還可以用于單個實例或類本身。它們使開發(fā)人員的意圖更加明顯后众,在調用方法或處理對象時胀糜,它們可以用于最小化錯誤颅拦。
實現(xiàn)方法
(1)類方法也可以使用裝飾器。實例方法是最常見的方法形式教藻,即類中的函數(shù)距帅。具體代碼,如下所示:
class Cat(object):
"""docstring for Cat"""
def __init__(self, breed, age):
super(Cat, self).__init__()
self.breed = breed
self.age = age
def cat_age(self):
return self.age
def breed(self):
return self.breed
def __repr__(self):
return '{breed}, {age}'.format(breed = self.breed, age = self.age)
(2)要使用這個類怖竭,需要創(chuàng)建一個Cat實例锥债,提供初始參數(shù)。
chip = Cat('domestic shorthair', 4)
(3)接下來痊臭,調用類方法哮肚,使其可以正常運行。
print(chip.age)
print(chip.breed)
(4)注意广匙,這些方法被綁定到了一個實例chip中允趟,因此,它們不能再泛型Cat類上調用鸦致。
print(Cat.age)
上面的寫法是錯誤的潮剪。
(5)靜態(tài)方法是應用于所有實例的方法。它們用方法定義之前的@staticmethod裝飾器標識分唾。另外抗碰,方法本身不需要定義self參數(shù)。
@staticmethod
def cry():
return '橘貓'
(6)靜態(tài)方法可以應用于實例和類本身绽乔,具體代碼弧蝇,如下所示:
class Cat(object):
"""docstring for Cat"""
def __init__(self, breed, age):
super(Cat, self).__init__()
self.breed = breed
self.age = age
def cat_age(self):
return self.age
def breed(self):
return self.breed
@staticmethod
def cry():
return '橘貓'
def __repr__(self):
return '{breed}, {age}'.format(breed = self.breed, age = self.age)
chip = Cat('橘貓', 10)
print(chip.cry)
print(chip.cry())
print(Cat.cry)
print(Cat.cry())
運行結果,如下所示:
<function Cat.cry at 0x0000017AF837D280>
橘貓
<function Cat.cry at 0x0000017AF837D280>
橘貓
當調用的是不帶括號的靜態(tài)方法會返回方法的內存位置折砸。方法沒有綁定到實例看疗,但是類頁可以使用它。只有在使用括號時睦授,才會顯示正確的返回對象两芳。
(7)類方法由創(chuàng)建方法之前的@classmethod標識。此外去枷,方法參數(shù)是cls而不是self怖辆。可以在前面的例子中的靜態(tài)方法之后添加以下代碼删顶。
class Cat(object):
"""docstring for Cat"""
def __init__(self, breed, age):
super(Cat, self).__init__()
self.breed = breed
self.age = age
def cat_age(self):
return self.age
def breed(self):
return self.breed
@staticmethod
def cry():
return '橘貓'
@classmethod
def type(cls):
if cls.__name__ == "Cat":
return "some sort of domestic cat"
else:
return cls.__name__
def __repr__(self):
return '{breed}, {age}'.format(breed = self.breed, age = self.age)
chip = Cat('橘貓',1)
print(chip.type())
運行結果疗隶,如下所示:
some sort of domestic cat
在創(chuàng)建實例時,將檢查它來自哪一個類翼闹。如果泛型Cat類是生成器,則會輸出一條消息蒋纬,如果使用Cat類的子類猎荠,則輸出該類名稱坚弱。
最后
第一期的裝飾器知識已經寫完了,但還沒有結束关摇。
敬請期待我下一期寫的裝飾器知識荒叶。
我想對觀看到這的小伙伴們說,文章的每一個字都是我用心敲出來的输虱,希望你可以點個【再看】些楣,讓我知道,你就是那個陪我一起努力的人宪睹。