Python 裝飾器詳解(中)
轉自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主僅對其中 demo 實現中不適合python3 版本的語法進行修改,并微調了排版,本轉載博客全部例程博主均已親測可行。
Python 3.8.5
ubuntu 18.04
聲明:此文章為,python裝飾器詳解——中篇,上一篇文章中,即詳解裝飾器——上篇 ,已經詳細講解了裝飾器誕生的背景,裝飾器的定義、作用、應用場景,本文將以實際例子為依托,深入詳解裝飾器的各類實現(包括函數裝飾器、類裝飾器、閉包、裝飾器的嵌套四大塊內容)系列文章共分為 上、中、下 三篇。此為第二篇。
一、函數裝飾器
前面提到過,裝飾器分為函數裝飾器、類裝飾器,本節詳細解釋函數裝飾器,又分為兩種情況,因為函數裝飾器可以裝飾函數,也可以裝飾類。函數裝飾器是最常見的,故而最先討論。
注意:因為沒有參數,且無函數返回值的函數是最為簡單的,就如“上篇”所討論的那樣,這里就不再敘述了,本文主要講的都是函數帶有參數,而且具有函數返回值的函數。
1. 函數裝飾器裝飾一個函數
假定有一個函數實現兩個數的相加,a+b,但是為了對這兩個數相加的結果進行加密,我們需要給函數添加額外的代碼,但是我們通過裝飾器去實現,要達到的效果是,不是直接返回a+b的結果,而是進行進一步處理。代碼如下:
def MethodDecoration(function): #外層decoratorc=150d=200def wrapper(a,b): #內層wrapper。和add_function參數要一樣result=function(a,b)result=result*c/d #加密,相當于添加額外功能return result #此處一定要返回值return wrapper@MethodDecoration
def add_function(a,b):return a+bresult=add_function(100,300) #函數調用
print(result)
運行結果為:300.0 ,即(100+300)*150/200。
因為函數裝飾器去裝飾函數最為常見,所以這里就不多再解釋了,按照前面上篇所講的模板來即可,但是因為被裝飾的函數有參數,而且具有返回值,有兩個點需要注意的:
-
wrapper
需要保證與add_function
參數一致。因為返回的wrapper
就是add_function
,所以要統一,我們可以使用*arg
和**kwargs
去匹配任何參數; -
wrapper
一定要返回值。因為add_function函數是需要返回值的。
2. 函數裝飾器裝飾一個類
在Python中,從某種意義上來說,函數和類是一樣的,因為它們都是對象(python一切皆對象),故而decorator的參數理所當然也可以傳入一個類了。其中最經典的應用,就是使用裝飾器構造“單例模式”(不明白單例模式的小伙伴可以參見下面這篇博文哦)
一文詳解“單例模式”及其python語言的實現
代碼如下:
def Singleton(cls): #這是第一層函數,相當于模板中的Decorator.目的是要實現一個“裝飾器”,而且是對類型的裝飾器'''cls:表示一個類名,即所要設計的單例類名稱,因為python一切皆對象,故而類名同樣可以作為參數傳遞'''instance = {}def singleton(*args, **kwargs): #這是第二層,相當于wrapper,要匹配參數if cls not in instance:instance[cls] = cls(*args, **kwargs) #如果沒有cls這個類,則創建,并且將這個cls所創建的實例,保存在一個字典中return instance[cls] #返回創建的對象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=' ')
運行結果為:
True
True
140506831296352 140506831296352
懂得單例模式的小伙伴可能一看就明白了,上面的實現和我前面講過的“裝飾器模板”,基本上一樣,第一層、第二層、返回值、參數匹配等。但是有的小伙伴可能會問,這里我沒有看到“添加額外功能”啊,裝飾器不是添加額外功能的么?實際上“添加額外功能”是一種抽象的表述,不是說一定要添加什么東西,對被裝飾的對象(函數、類)進行某種約束、處理、添加、刪減等額外操作統稱為添加額外功能。
這里約束了這個類型Student的創建,主要這個類還沒有創建實例,就創建一個,只要創建了,就不在創建新的實例了,只需要把之前創建的返回即可,這是單例模式的思想。如果還是不明白,下面再舉一個“添加額外功能”的例子。
比如我有一個學生類,在創建學生實例的時候有兩個實例屬性,name,age,現在要通過裝飾器對類加以裝飾,使得在創建學生類的實例的時候,還會添加height和weight兩個屬性,代碼如下:
def ClassDecorator(cls): #第一層函數decoratorheight=170weight=65def wrapper(name,age): #第二層函數wrapper,參數要和類的構造函數匹配s=cls(name,age)s.height=height #添加兩個額外屬性s.weight=weightreturn s #返回創建的對象,因為類的構造函數是要返回實例的,即有返回值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中可能會有提示此處錯誤,學生沒有height和weight屬性,但是運行之后沒錯
print(stu.weight) #這就是python的魅力,動態添加屬性
運行結果為:
張三
25
170
65
上面的例子和我們前面講的裝飾函數實在是太像了,基本上和前面講的模板一模一樣。
**總結:**函數裝飾器不管是裝飾函數、還是裝飾類,所遵循的思想原理是一樣的,實現的方式也是大同小異。注意,函數裝飾器裝飾類,實際上是裝飾類的構造函數哦!
二、類裝飾器
前面定義的裝飾器都是函數,實際上,類也可以是一個裝飾器,同樣的道理,類裝飾器既可以裝飾函數,也可以裝飾類。
1. 類裝飾器裝飾函數
先從一個簡單的例子說起,代碼如下:
class MethodDecorator:def __init__(self,function):self.function=functiondef __call__(self):print('開始')self.function()print('結束')@MethodDecorator
def myfunc():print('我是函數myfunc')myfunc()
運行結果為:
開始
我是函數myfunc
結束
可能有的小伙伴很懵逼,怎么會得到這樣的結果?我們一句一句來分析。
@MethodDecorator
def myfunc():print('我是函數myfunc')myfunc()
這里相當于 myfunc=MethodDecorator(myfunc)
,這樣一寫就明白了,首先myfunc
函數作為參數傳遞給類的構造函數,因為調用類的構造函數自然會返回類的一個實例對象,所以前面的myfunc
實際上是類的一個實例對象,然后調用myfunc
,這里雖然從形式上看依然是看起來還是調用函數,但是本質上已經發生了變化,它實際上一個對象調用(這是類裝飾器的本質,很重要),這就是為什么要定義__call__
魔法方法。下面比如函數有返回值,而且有參數,要用類裝飾器去裝飾這個函數,再用一個實例說明,依然以上面的兩個數據值和進行加密為例。
class MethodDecorator:def __init__(self,function): #這里相當于是第一層,作用是將函數function傳遞進來self.function=functionself.c=150 #這兩個是需要加密的額外信息self.d=200def __call__(self,a,b): #這相當于是第二層的wrapperprint('開始')result=self.function(a,b)result=result*self.c/self.dprint('結束')return result #返回值@MethodDecorator
def add_function(a,b):return a+bresult=add_function(100,300) #這里相當于是函數調用
print(result)
運行結果為:
開始
結束
300.0
總結:實際上類裝飾器所實現的功能在原理上和函數裝飾器也沒有太大的區別,但是在代碼實現上有所區別,主要體現在兩方面:
-
類裝飾器的構造函數
__init__
就相當于是第一層(外層)的decorator
,傳入需要裝飾的對象作為參數; -
類裝飾器的魔法方法
__call__
相當于是第二層(內層)的wrapper
。注意參數要統一,有返回值需要返回值。
2. 類裝飾器裝飾類
依然以上面的給學生添加額外屬性為例
class ClassDecorator:def __init__(self,cls): #這里相當于是第一層,作用是將類名Student傳遞進來self.cls=clsself.height=170self.weight=65def __call__(self,name,age): #這相當于是第二層的wrappers=self.cls(name,age)s.height=self.height #動態添加屬性,增加額外信息s.weight=self.weightreturn s #返回創建的學生實例s@ClassDecorator
class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('張三',25) #注意:這里的Student其實并不是一個類了,而是裝飾器返回的一個對象,即這里的Student是ClassDecorator的實例#而且,這里的Student('張三',25) 也不是構造函數了,它的本質是裝飾類的“對象調用”
print(stu.name)
print(stu.age)
print(stu.height)
print(stu.weight)
輸出結果為:
張三
25
170
65
總結:這里的Student其實并不是一個類了,而是裝飾器返回的一個對象,即這里的Student是ClassDecorator的實例,而且,這里的Student(‘張三’,25) 也不是構造函數了,它的本質是裝飾類的“對象調用”。
三、類裝飾器的一般模板
通過上面的例子,不管類裝飾器是裝飾類,還是裝飾函數,它的模板都是大同小異的,如下所示:
class ClassDecorator: #類裝飾器的名稱def __init__(self,function_or_cls): #這里相當于是第一層,作用是將需要裝飾的類名、或者是函數名傳遞進來#這里可以添加額外信息self.cls=cls #或者是self.function=function,本質是要構造 一個屬性#這里可以添加額外信息def __call__(self,name,age): #這相當于是第二層的wrapper,參數需要與被裝飾的類、被裝飾的函數,參數相同#這里可以增加額外信息s=self.cls(name,age) #本質是調用原來的函數或者類的構造函數#result=self.function(a,b) #這里可以增加額外信息return s #返回創建的學生實例s
注意:類裝飾器,對象調用__call__
是不可或缺的哦。
四、裝飾器的缺點
前面講了一大堆裝飾器的優點:簡化代碼,代碼復用;增加額外功能等。那么裝飾器優缺點嗎,當然有了,世界上就沒有完美無缺的東西!那到底有一些什么樣的缺點呢?其實在上面的表述中已經提到了,不知道小伙伴有沒有注意!
(以下代碼在上面的代碼代碼基礎上執行,上面代碼這里就不重復一遍了)
1. 函數裝飾器裝飾函數的時候
在上面源代碼的基礎之上添加下面的代碼:
print(add_function.__name__)
輸出為:
wrapper
這是為什么,如果add_function
沒有被裝飾器修飾的話,則返回的應該為add_function
,這里為什么會返回第二層包裝函數wrapper
的名稱?這是因為裝飾器的本質是add_function=MethodDecoration(add_function)
,而MethodDecoration
返回的本來就是wrapper
,這就是上面結果的解釋了。
2. 函數裝飾器裝飾類的時候
同樣添加一句代碼
print(Student.__name__)
返回的結果是:wrapper
出現這個現象的原因同上面1中所述的,完全一樣。
3. 類裝飾器裝飾類的時候
同樣的添加下面一句話
print(add_function.__name__) #這里IDE不會提示錯誤哦,IDE依然覺得這是個函數,應該有__name__才對的
顯示:
AttributeError: 'MethodDecorator' object has no attribute '__name__'
這里為什么突然不一樣了呢?正如前面所說的,這里的add_function
本質上是add_function=MethodDecorator(add_function)
,所以add_function
本質上是裝飾類的一個實例,而MethodDecorator
沒有定義__name__
屬性,那自然調用add_function.__name__
就會顯示沒有__name__
這個屬性了。
4. 類裝飾器裝飾類的時候
print(Student.__name__) #這里IDE不會提示錯誤哦,IDE依然覺得這是個類名,應該有__name__才對的
顯示:
AttributeError: 'ClassDecorator' object has no attribute '__name__'
原因同上面一樣,因為Student本質上是ClassDecorator的一個對象實例哦!
裝飾器的缺點總結:
-
被函數裝飾器所裝飾的對象(函數、類)已經不再是它本身了,雖然從形式上看沒有變化,本質上是函數裝飾器的內部wrapper;
-
被類裝飾器所裝飾的對象(函數、類)也不再是它本身了,雖然從形式上看沒有變化,本質上是類裝飾器的一個對象。
補充:關于裝飾器的嵌套,裝飾器與python閉包的關系,我會在系列文章:Python高級編程——裝飾器Decorator詳解(下篇)中繼續講解,有興趣的繼續關注!