Python 装饰器详解(中)
Python 裝飾器詳解(中)
轉(zhuǎn)自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主僅對(duì)其中 demo 實(shí)現(xiàn)中不適合python3 版本的語(yǔ)法進(jìn)行修改,并微調(diào)了排版,本轉(zhuǎn)載博客全部例程博主均已親測(cè)可行。
Python 3.8.5
ubuntu 18.04
聲明:此文章為,python裝飾器詳解——中篇,上一篇文章中,即詳解裝飾器——上篇 ,已經(jīng)詳細(xì)講解了裝飾器誕生的背景,裝飾器的定義、作用、應(yīng)用場(chǎng)景,本文將以實(shí)際例子為依托,深入詳解裝飾器的各類(lèi)實(shí)現(xiàn)(包括函數(shù)裝飾器、類(lèi)裝飾器、閉包、裝飾器的嵌套四大塊內(nèi)容)系列文章共分為 上、中、下 三篇。此為第二篇。
一、函數(shù)裝飾器
前面提到過(guò),裝飾器分為函數(shù)裝飾器、類(lèi)裝飾器,本節(jié)詳細(xì)解釋函數(shù)裝飾器,又分為兩種情況,因?yàn)楹瘮?shù)裝飾器可以裝飾函數(shù),也可以裝飾類(lèi)。函數(shù)裝飾器是最常見(jiàn)的,故而最先討論。
注意:因?yàn)闆](méi)有參數(shù),且無(wú)函數(shù)返回值的函數(shù)是最為簡(jiǎn)單的,就如“上篇”所討論的那樣,這里就不再敘述了,本文主要講的都是函數(shù)帶有參數(shù),而且具有函數(shù)返回值的函數(shù)。
1. 函數(shù)裝飾器裝飾一個(gè)函數(shù)
假定有一個(gè)函數(shù)實(shí)現(xiàn)兩個(gè)數(shù)的相加,a+b,但是為了對(duì)這兩個(gè)數(shù)相加的結(jié)果進(jìn)行加密,我們需要給函數(shù)添加額外的代碼,但是我們通過(guò)裝飾器去實(shí)現(xiàn),要達(dá)到的效果是,不是直接返回a+b的結(jié)果,而是進(jìn)行進(jìn)一步處理。代碼如下:
def MethodDecoration(function): #外層decoratorc=150d=200def wrapper(a,b): #內(nèi)層wrapper。和add_function參數(shù)要一樣result=function(a,b)result=result*c/d #加密,相當(dāng)于添加額外功能return result #此處一定要返回值return wrapper@MethodDecoration def add_function(a,b):return a+bresult=add_function(100,300) #函數(shù)調(diào)用 print(result)運(yùn)行結(jié)果為:300.0 ,即(100+300)*150/200。
因?yàn)楹瘮?shù)裝飾器去裝飾函數(shù)最為常見(jiàn),所以這里就不多再解釋了,按照前面上篇所講的模板來(lái)即可,但是因?yàn)楸谎b飾的函數(shù)有參數(shù),而且具有返回值,有兩個(gè)點(diǎn)需要注意的:
wrapper需要保證與add_function參數(shù)一致。因?yàn)榉祷氐膚rapper就是add_function,所以要統(tǒng)一,我們可以使用*arg和**kwargs去匹配任何參數(shù);
wrapper一定要返回值。因?yàn)閍dd_function函數(shù)是需要返回值的。
2. 函數(shù)裝飾器裝飾一個(gè)類(lèi)
在Python中,從某種意義上來(lái)說(shuō),函數(shù)和類(lèi)是一樣的,因?yàn)樗鼈兌际菍?duì)象(python一切皆對(duì)象),故而decorator的參數(shù)理所當(dāng)然也可以傳入一個(gè)類(lèi)了。其中最經(jīng)典的應(yīng)用,就是使用裝飾器構(gòu)造“單例模式”(不明白單例模式的小伙伴可以參見(jiàn)下面這篇博文哦)
一文詳解“單例模式”及其python語(yǔ)言的實(shí)現(xiàn)
代碼如下:
def Singleton(cls): #這是第一層函數(shù),相當(dāng)于模板中的Decorator.目的是要實(shí)現(xiàn)一個(gè)“裝飾器”,而且是對(duì)類(lèi)型的裝飾器'''cls:表示一個(gè)類(lèi)名,即所要設(shè)計(jì)的單例類(lèi)名稱(chēng),因?yàn)閜ython一切皆對(duì)象,故而類(lèi)名同樣可以作為參數(shù)傳遞'''instance = {}def singleton(*args, **kwargs): #這是第二層,相當(dāng)于wrapper,要匹配參數(shù)if cls not in instance:instance[cls] = cls(*args, **kwargs) #如果沒(méi)有cls這個(gè)類(lèi),則創(chuàng)建,并且將這個(gè)cls所創(chuàng)建的實(shí)例,保存在一個(gè)字典中return instance[cls] #返回創(chuàng)建的對(duì)象return singleton@Singleton class Student(object):def __init__(self, name,age):self.name=nameself.age=ages1 = Student('張三',23) s2 = Student('李四',24) print((s1==s2)) print(s1 is s2) print(id(s1),id(s2),sep=' ')運(yùn)行結(jié)果為:
True True 140506831296352 140506831296352懂得單例模式的小伙伴可能一看就明白了,上面的實(shí)現(xiàn)和我前面講過(guò)的“裝飾器模板”,基本上一樣,第一層、第二層、返回值、參數(shù)匹配等。但是有的小伙伴可能會(huì)問(wèn),這里我沒(méi)有看到“添加額外功能”啊,裝飾器不是添加額外功能的么?實(shí)際上“添加額外功能”是一種抽象的表述,不是說(shuō)一定要添加什么東西,對(duì)被裝飾的對(duì)象(函數(shù)、類(lèi))進(jìn)行某種約束、處理、添加、刪減等額外操作統(tǒng)稱(chēng)為添加額外功能。
這里約束了這個(gè)類(lèi)型Student的創(chuàng)建,主要這個(gè)類(lèi)還沒(méi)有創(chuàng)建實(shí)例,就創(chuàng)建一個(gè),只要?jiǎng)?chuàng)建了,就不在創(chuàng)建新的實(shí)例了,只需要把之前創(chuàng)建的返回即可,這是單例模式的思想。如果還是不明白,下面再舉一個(gè)“添加額外功能”的例子。
比如我有一個(gè)學(xué)生類(lèi),在創(chuàng)建學(xué)生實(shí)例的時(shí)候有兩個(gè)實(shí)例屬性,name,age,現(xiàn)在要通過(guò)裝飾器對(duì)類(lèi)加以裝飾,使得在創(chuàng)建學(xué)生類(lèi)的實(shí)例的時(shí)候,還會(huì)添加height和weight兩個(gè)屬性,代碼如下:
def ClassDecorator(cls): #第一層函數(shù)decoratorheight=170weight=65def wrapper(name,age): #第二層函數(shù)wrapper,參數(shù)要和類(lèi)的構(gòu)造函數(shù)匹配s=cls(name,age)s.height=height #添加兩個(gè)額外屬性s.weight=weightreturn s #返回創(chuàng)建的對(duì)象,因?yàn)轭?lèi)的構(gòu)造函數(shù)是要返回實(shí)例的,即有返回值return wrapper@ClassDecorator class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('張三',25) print(stu.name) print(stu.age) print(stu.height) #在 IDE中可能會(huì)有提示此處錯(cuò)誤,學(xué)生沒(méi)有height和weight屬性,但是運(yùn)行之后沒(méi)錯(cuò) print(stu.weight) #這就是python的魅力,動(dòng)態(tài)添加屬性運(yùn)行結(jié)果為:
張三 25 170 65上面的例子和我們前面講的裝飾函數(shù)實(shí)在是太像了,基本上和前面講的模板一模一樣。
**總結(jié):**函數(shù)裝飾器不管是裝飾函數(shù)、還是裝飾類(lèi),所遵循的思想原理是一樣的,實(shí)現(xiàn)的方式也是大同小異。注意,函數(shù)裝飾器裝飾類(lèi),實(shí)際上是裝飾類(lèi)的構(gòu)造函數(shù)哦!
二、類(lèi)裝飾器
前面定義的裝飾器都是函數(shù),實(shí)際上,類(lèi)也可以是一個(gè)裝飾器,同樣的道理,類(lèi)裝飾器既可以裝飾函數(shù),也可以裝飾類(lèi)。
1. 類(lèi)裝飾器裝飾函數(shù)
先從一個(gè)簡(jiǎn)單的例子說(shuō)起,代碼如下:
class MethodDecorator:def __init__(self,function):self.function=functiondef __call__(self):print('開(kāi)始')self.function()print('結(jié)束')@MethodDecorator def myfunc():print('我是函數(shù)myfunc')myfunc()運(yùn)行結(jié)果為:
開(kāi)始 我是函數(shù)myfunc 結(jié)束可能有的小伙伴很懵逼,怎么會(huì)得到這樣的結(jié)果?我們一句一句來(lái)分析。
@MethodDecorator def myfunc():print('我是函數(shù)myfunc')myfunc()這里相當(dāng)于 myfunc=MethodDecorator(myfunc),這樣一寫(xiě)就明白了,首先myfunc函數(shù)作為參數(shù)傳遞給類(lèi)的構(gòu)造函數(shù),因?yàn)檎{(diào)用類(lèi)的構(gòu)造函數(shù)自然會(huì)返回類(lèi)的一個(gè)實(shí)例對(duì)象,所以前面的myfunc實(shí)際上是類(lèi)的一個(gè)實(shí)例對(duì)象,然后調(diào)用myfunc,這里雖然從形式上看依然是看起來(lái)還是調(diào)用函數(shù),但是本質(zhì)上已經(jīng)發(fā)生了變化,它實(shí)際上一個(gè)對(duì)象調(diào)用(這是類(lèi)裝飾器的本質(zhì),很重要),這就是為什么要定義__call__魔法方法。下面比如函數(shù)有返回值,而且有參數(shù),要用類(lèi)裝飾器去裝飾這個(gè)函數(shù),再用一個(gè)實(shí)例說(shuō)明,依然以上面的兩個(gè)數(shù)據(jù)值和進(jìn)行加密為例。
class MethodDecorator:def __init__(self,function): #這里相當(dāng)于是第一層,作用是將函數(shù)function傳遞進(jìn)來(lái)self.function=functionself.c=150 #這兩個(gè)是需要加密的額外信息self.d=200def __call__(self,a,b): #這相當(dāng)于是第二層的wrapperprint('開(kāi)始')result=self.function(a,b)result=result*self.c/self.dprint('結(jié)束')return result #返回值@MethodDecorator def add_function(a,b):return a+bresult=add_function(100,300) #這里相當(dāng)于是函數(shù)調(diào)用 print(result)運(yùn)行結(jié)果為:
開(kāi)始 結(jié)束 300.0總結(jié):實(shí)際上類(lèi)裝飾器所實(shí)現(xiàn)的功能在原理上和函數(shù)裝飾器也沒(méi)有太大的區(qū)別,但是在代碼實(shí)現(xiàn)上有所區(qū)別,主要體現(xiàn)在兩方面:
類(lèi)裝飾器的構(gòu)造函數(shù)__init__就相當(dāng)于是第一層(外層)的 decorator,傳入需要裝飾的對(duì)象作為參數(shù);
類(lèi)裝飾器的魔法方法__call__相當(dāng)于是第二層(內(nèi)層)的 wrapper。注意參數(shù)要統(tǒng)一,有返回值需要返回值。
2. 類(lèi)裝飾器裝飾類(lèi)
依然以上面的給學(xué)生添加額外屬性為例
class ClassDecorator:def __init__(self,cls): #這里相當(dāng)于是第一層,作用是將類(lèi)名Student傳遞進(jìn)來(lái)self.cls=clsself.height=170self.weight=65def __call__(self,name,age): #這相當(dāng)于是第二層的wrappers=self.cls(name,age)s.height=self.height #動(dòng)態(tài)添加屬性,增加額外信息s.weight=self.weightreturn s #返回創(chuàng)建的學(xué)生實(shí)例s@ClassDecorator class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('張三',25) #注意:這里的Student其實(shí)并不是一個(gè)類(lèi)了,而是裝飾器返回的一個(gè)對(duì)象,即這里的Student是ClassDecorator的實(shí)例#而且,這里的Student('張三',25) 也不是構(gòu)造函數(shù)了,它的本質(zhì)是裝飾類(lèi)的“對(duì)象調(diào)用” print(stu.name) print(stu.age) print(stu.height) print(stu.weight)輸出結(jié)果為:
張三 25 170 65總結(jié):這里的Student其實(shí)并不是一個(gè)類(lèi)了,而是裝飾器返回的一個(gè)對(duì)象,即這里的Student是ClassDecorator的實(shí)例,而且,這里的Student(‘張三’,25) 也不是構(gòu)造函數(shù)了,它的本質(zhì)是裝飾類(lèi)的“對(duì)象調(diào)用”。
三、類(lèi)裝飾器的一般模板
通過(guò)上面的例子,不管類(lèi)裝飾器是裝飾類(lèi),還是裝飾函數(shù),它的模板都是大同小異的,如下所示:
class ClassDecorator: #類(lèi)裝飾器的名稱(chēng)def __init__(self,function_or_cls): #這里相當(dāng)于是第一層,作用是將需要裝飾的類(lèi)名、或者是函數(shù)名傳遞進(jìn)來(lái)#這里可以添加額外信息self.cls=cls #或者是self.function=function,本質(zhì)是要構(gòu)造 一個(gè)屬性#這里可以添加額外信息def __call__(self,name,age): #這相當(dāng)于是第二層的wrapper,參數(shù)需要與被裝飾的類(lèi)、被裝飾的函數(shù),參數(shù)相同#這里可以增加額外信息s=self.cls(name,age) #本質(zhì)是調(diào)用原來(lái)的函數(shù)或者類(lèi)的構(gòu)造函數(shù)#result=self.function(a,b) #這里可以增加額外信息return s #返回創(chuàng)建的學(xué)生實(shí)例s注意:類(lèi)裝飾器,對(duì)象調(diào)用__call__是不可或缺的哦。
四、裝飾器的缺點(diǎn)
前面講了一大堆裝飾器的優(yōu)點(diǎn):簡(jiǎn)化代碼,代碼復(fù)用;增加額外功能等。那么裝飾器優(yōu)缺點(diǎn)嗎,當(dāng)然有了,世界上就沒(méi)有完美無(wú)缺的東西!那到底有一些什么樣的缺點(diǎn)呢?其實(shí)在上面的表述中已經(jīng)提到了,不知道小伙伴有沒(méi)有注意!
(以下代碼在上面的代碼代碼基礎(chǔ)上執(zhí)行,上面代碼這里就不重復(fù)一遍了)
1. 函數(shù)裝飾器裝飾函數(shù)的時(shí)候
在上面源代碼的基礎(chǔ)之上添加下面的代碼:
print(add_function.__name__)輸出為:
wrapper這是為什么,如果add_function沒(méi)有被裝飾器修飾的話,則返回的應(yīng)該為add_function,這里為什么會(huì)返回第二層包裝函數(shù)wrapper的名稱(chēng)?這是因?yàn)?font color="red">裝飾器的本質(zhì)是add_function=MethodDecoration(add_function),而MethodDecoration返回的本來(lái)就是wrapper,這就是上面結(jié)果的解釋了。
2. 函數(shù)裝飾器裝飾類(lèi)的時(shí)候
同樣添加一句代碼
print(Student.__name__)返回的結(jié)果是:wrapper
出現(xiàn)這個(gè)現(xiàn)象的原因同上面1中所述的,完全一樣。
3. 類(lèi)裝飾器裝飾類(lèi)的時(shí)候
同樣的添加下面一句話
print(add_function.__name__) #這里IDE不會(huì)提示錯(cuò)誤哦,IDE依然覺(jué)得這是個(gè)函數(shù),應(yīng)該有__name__才對(duì)的顯示:
AttributeError: 'MethodDecorator' object has no attribute '__name__'這里為什么突然不一樣了呢?正如前面所說(shuō)的,這里的add_function本質(zhì)上是add_function=MethodDecorator(add_function),所以add_function本質(zhì)上是裝飾類(lèi)的一個(gè)實(shí)例,而MethodDecorator沒(méi)有定義__name__屬性,那自然調(diào)用add_function.__name__就會(huì)顯示沒(méi)有__name__這個(gè)屬性了。
4. 類(lèi)裝飾器裝飾類(lèi)的時(shí)候
print(Student.__name__) #這里IDE不會(huì)提示錯(cuò)誤哦,IDE依然覺(jué)得這是個(gè)類(lèi)名,應(yīng)該有__name__才對(duì)的顯示:
AttributeError: 'ClassDecorator' object has no attribute '__name__'原因同上面一樣,因?yàn)镾tudent本質(zhì)上是ClassDecorator的一個(gè)對(duì)象實(shí)例哦!
裝飾器的缺點(diǎn)總結(jié):
被函數(shù)裝飾器所裝飾的對(duì)象(函數(shù)、類(lèi))已經(jīng)不再是它本身了,雖然從形式上看沒(méi)有變化,本質(zhì)上是函數(shù)裝飾器的內(nèi)部wrapper;
被類(lèi)裝飾器所裝飾的對(duì)象(函數(shù)、類(lèi))也不再是它本身了,雖然從形式上看沒(méi)有變化,本質(zhì)上是類(lèi)裝飾器的一個(gè)對(duì)象。
補(bǔ)充:關(guān)于裝飾器的嵌套,裝飾器與python閉包的關(guān)系,我會(huì)在系列文章:Python高級(jí)編程——裝飾器Decorator詳解(下篇)中繼續(xù)講解,有興趣的繼續(xù)關(guān)注!
總結(jié)
以上是生活随笔為你收集整理的Python 装饰器详解(中)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 片仔癀股票价格会涨到500以上吗 通过
- 下一篇: Python 装饰器详解(上)