修飾器(裝飾器)在Python中也是一個很重要的內容,它可以讓其他函式在不需要做任何代碼變動的前提下增加額外功能,相當于一個語法糖,可能在新手看來,這是一個難以理解或者不知道有啥用的存在,但后面你多多少少都要碰到或者用到它的,

一、修飾器的概念和作用
1.什么是修飾器?
修飾器又叫裝飾器,本身也是一個函式,是在原有的函式或者是方法上增添一些額外的功能,
2.修飾器的作用
概括的講,裝飾器的作用就是為已經存在的物件添加額外的功能,
比如說這個函式是注冊的功能,但有時候這個用戶在執行這個操作的時候,他是已注冊的用戶,我這個函式已經寫好了,不想動它了,那么我們就可以通過修飾器來給這個函式增加一個登錄的功能,
它經常用于有切面需求的場景,比如:插入日志、性能測驗、事務處理、快取、權限校驗等場景,裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函式功能本身無關的雷同代碼并繼續重用,

修飾器的具體操作,我們來慢慢學習,
二、修飾器的使用
1.使用說明
在使用修飾器之前,我們得記住幾個關于修飾器的使用說明:
(1)修飾器的關鍵字是 @ ,Python代碼中只要出現了它,你就可以想到是修飾器了,
(2)修飾器修飾的是函式或者是方法,不能修飾一個類,
(3)修飾器必須出現在被修飾函式或者方法的前一行,不能夠將修飾器定義在函式的同行,
例子:

雖然修飾器本身是一個函式,但它的出現是有規定的,我上面的修飾器就沒有出現在被修飾函式或者方法的前1行,所以連 print(“龍叔”) 這一行代碼都執行不了,
(4)修飾器本身是一個函式,將被修飾的函式作為引數,傳遞給修飾器,執行修飾器中功能,回傳傳遞進來的函式物件,呼叫回傳出來的函式,
這幾個點很重要,下面我們通過修飾器的多種使用方式來加深理解,

2.基本使用方式
如果被修飾的函式不呼叫,則執行@后面的函式,并把被修飾的函式當做引數傳遞過去了,則修飾器函式的回傳值可以是任意值,
例子:
def dec(a): #修飾器函式
print(a) #看一下形參傳了什么
print("helloworld")
return None
@dec #使用修飾器
def funA(): #被修飾的函式
pass
運行結果:
<function funA at 0x0000018D849BB670>
helloworld
首先我們可以看出來,這就是很簡單的一個修飾器的使用例子,用 @dec 來呼叫dec()修飾器函式,而被修飾的函式funA()是沒有進行什么操作的,
其次我們再看,被修飾的函式funA()不呼叫什么,但卻被當做引數傳遞給了修飾器函式dec(),所以dec()需要一個形參來接受傳遞值,我用的是a作為形參,如果你去掉a的話,系統就會報錯,因為被修飾的函式funA()回傳的值沒有東西接受,你可以試試,
最后還有1點,被修飾函式funA()并沒有被呼叫,注意到了嗎?所以修飾器函式dec()回傳什么都可以,上面回傳的是None,你回傳一個“有病”也可以的,回傳什么都可以的,下面我們再看被修飾函式 funA() 被呼叫的情況,

如果被修飾的函式呼叫了,直接執行修飾器,并把被修飾的函式當做引數專遞給修飾器,但是修飾器的回傳值必須是當前引數,
例子:
def dec(a):
print(a)
print("helloworld")
return "有病"
@dec #使用修飾器函式
def funA():
pass
funA() #呼叫被修飾函式
運行結果:
<function funA at 0x000001F78A48B670>
helloworld
Traceback (most recent call last):
File "E:\Python-learn\可迭代物件\修飾器\修飾器_test.py", line 11, in <module>
funA()
TypeError: 'str' object is not callable
出現錯誤了,說字串str不能被迭代,為什么?其實原因就是有修飾器存在并使用的情況下,被修飾函式又被呼叫了,這個時候回傳值就不能是任意值了,此時的修飾器函數只能回傳傳遞值,你可以試試把 return “有病” 改成 return a 就可以正常輸出了,又或者你去掉 funA() 這一行代碼,不呼叫被修斯函式,則輸出也是正常的,
def dec(a):
print(a)
print("helloworld")
return a
@dec
def funA():
pass
funA()
運行結果:
<function funA at 0x0000020F5664B670>
helloworld
一切正常,
我們再來看看修飾器的執行邏輯是怎么樣的:
def dec(a):
print(a)
print("修飾器函式")
return a
@dec
def funA():
print("被修飾函式")
funA()
print("龍叔")
運行結果:
<function funA at 0x000001D90E75B670>
修飾器函式
被修飾函式
龍叔
從這個運行結果我們可以看出修飾器的運行邏輯是:被修飾的函式 funA() 被呼叫了,但卻沒有被直接執行,而是直接執行修飾器dec,并把被修飾的函式 funA() 當做引數專遞給修飾器dec,執行修飾函式dec()里面的代碼,回傳傳遞值a后才執行的被修飾函式funA()里面的代碼,最后走完了被修飾函式funA() 才執行剩余的代碼,
我用個比較草一點的圖來表示一下:

3.其他使用方式:函式嵌套
前面我們講過了修飾器的基本使用方式,修飾器的使用方式很多,還可以使用函式嵌套的方式,
用個簡單例子來演示一下:
def A(x): #修飾器函式
def B(): #修飾器函式里面嵌套的函式
print("B")
B()
return x
@A #使用修飾器
def C(): #被修飾函式
print("C")
C() #呼叫被修飾函式
運行結果:
B
C
從運行結果來看,被修飾函式里面也是可以進行函式嵌套的,
4.其他使用方式:閉包
閉包的也是屬于函式里的一種,只是比較特殊,關于閉包的知識我這里就不復述了,在【Python基礎】里面我們有講過關于閉包的知識,忘了的可以去看看或者百度一下,我們來看看修飾器里面的閉包是怎么使用的,
def A(x):
def B():
print("B")
return x() # C 無差別呼叫只能一層,這里是無法通過x呼叫
return B
@A
def C():
print("C")
C()
運行結果:
B
C
可以看出在修飾器函式中,閉包也是可以正常使用的,

5.其他使用方式:被修飾的函式有引數的形式
如果被修飾的函式有引數傳遞,引數只能傳給修飾器函式里面的內嵌函式,
def A(x): #修飾器函式
print(x)
def B(aa, bbb): # 內嵌函式,接收被修飾函式傳遞的引數
print("B")
print(aa, bbb)
return x(aa, bbb) # C
return B
@A
def C(n, nn): #被修飾函式
print("C")
C("10", "20")
運行結果:
<function C at 0x00000206BED6B670>
B
10 20
C
可以看出來,雖然被修飾函式 C()傳遞了參數給修飾器函式 A() ,但是默認傳遞的還是C()這個物件,修飾器函式A()接受的還是被修飾函式C(),引數傳到了修飾器函式A()里面的函式B(),
就算你在A()里面加兩個形參,它也接受不了,只會報錯,前面在修飾器的使用說明我們已經說了,“被修飾的函式作為引數,傳遞給修飾器”,所以修飾器函式接受的只是被修飾函式這個物件,其他如果要傳遞引數,那么修飾器函式里面就就得有其他函式來接受傳遞的引數,
6.其他使用方式:有引數的修飾器,無引數的函式,使用內嵌函式收取引數
如果修飾器有引數但被修飾函式卻沒有引數的情況下,只能使用內嵌函式來收取引數,
def fun(a=20):
print(a)
def c(bb):
print(bb)
return bb # 可以無差別呼叫,因為是在第二層才接收的funB,相當于第一層
return c
@fun(30)
def funB():
print("xixixi")
funB()
運行結果:
30
<function funB at 0x0000025DAE4DD0D0>
xixixi
三、Python內置的修飾器
前面我們所講的都是我們自定義的修飾器,在Python中是有內置的修飾器,我們來學習一下常見的三種Python內置修飾器:staticmethod,classmethod,property,
它們的作用就是把類中的方法變為靜態方法,包括類的類屬性和類方法,具體的使用,我們來簡單的看一下,
1.property
property可以將方法變為屬性,被修飾的方法名必須和property下方的方法名一樣,property只能用于私有屬性,
我們來做一個對比,我們在不使用property的情況下,創建一個類并使用類里面的方法,是這樣的:
class A:
def __init__(self):
self.age = 20 #實體屬性
def getattr(self): #列印實體屬性
print(self.age)
def setattr(self,newage): #給實體屬性賦值
self.age = newage
a = A()
a.setattr(30)
a.getattr()
運行結果:
30
沒有什么問題,運行結果正常,那我們用內置修飾器property來試試看有何不同,
在使用內置修飾器property之前,我們得補充一個點:私有屬性,在函式內部創建的屬性是私有屬性,在不經過特殊處理之前,無法在函式外部使用,私有屬性前面加兩個下劃線表示,比如類里面的__name就表示私有屬性,我們通過一個簡單的例子來看看:

可以看出來,私有屬性在類內部是可以進行訪問的,在外部是不行的(做了一些處理之后是可以訪問的,這里就不介紹了,可以網上查一下),
回到我們的內置修飾器property,在前面不使用property的例子中,我們做一下修改,加入內置修飾器property,
class A:
def __init__(self):
self.__age = 20
@property
def age(self): #被修飾的方法
return self.__age
@age.getter
def age(self): #被修飾的方法
return self.__age
@age.setter
def age(self, newage): #被修飾的方法
self.__age = newage
a = A()
a.age = 200
print(a.age)
運行結果:
200
從這個例子我們可以看出,內置修飾器property是可以用于私有屬性,并且可以將方法變為屬性,比如a.age就是在呼叫屬性而不是方法了,在類中雖然多次出現了age()這個方法但卻沒有因為方法覆寫而報錯,也是因為property的存在,property規定了被修飾的方法名必須和property下方的方法名一樣,

2.staticmethod – 靜態方法
內置修飾器staticmethod是一個靜態方法,功能是將被修飾的方法從類中抽離出來,成為獨立的函式,該函式不能訪問類的屬性,
我們先寫一個簡單的類,通過使用與不使用staticmethod來做一個對比,不使用的情況下
class B:
def __init__(self, name):
self.name = name
def eat(self): # 列印傳遞的值
print(self.name)
b = B("龍叔")
b.eat()
運行結果:
龍叔
這個沒有什么問題,再來看看使用了staticmethod,直接加上看看:

報錯了,原因就是加上了@staticmethod 后,eat()這個方法就變成了一個普通的函式,它的位置雖然在類里面,但實際上卻相當于一個普通的函式,并不是類的方法了,所以你得給它傳遞一個值,不然形參self就沒有值傳遞從而報錯了,
正確的寫法應該是:

我們來總結一下這個staticmethod 靜態方法:
1. 這個函式是一個普通函式,只有這個類能用
2. 靜態方法可以設定引數,也可以不需要引數了(self)
3. 該函式不能訪問類的屬性
3.classmethod
被classmethod修飾的方法,與實體方法的區別是接收的第一個引數不是self,而是cls(當前類的具體型別),被修飾的方法無法訪問實體屬性,但是可以訪問類屬性,
class B:
age = 10
def __init__(self, name):
self.name = name
def sleep(self): #列印
print(self)
@classmethod
def eat(cls): # 被修飾的函式
print(cls) #看看傳遞的是類還是值
print(cls.age) #訪問類的屬性
print(self.name) #訪問實體物件的屬性
b = B("龍叔")
b.sleep()
b.eat()
運行結果:
<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
File "D:\pythonProject1\專題2.py", line 21, in <module>
b.eat()
File "D:\pythonProject1\專題2.py", line 14, in eat
print(self.name)
NameError: name 'self' is not defined
通過結果可以看出,從sleep()和eat() 列印的物件來看,sleep()傳遞的物件是創建的實體物件b,而被修飾器修飾的函式eat()傳遞的是類;從eat()訪問類的屬性和實體屬性來看,訪問類的屬性是沒有問題的,但訪問實體物件的屬性時就報錯了,
所以驗證了前面講的:被classmethod修飾的方法,與實體方法的區別是接收的第一個引數不是self,而是cls(當前類的具體型別),被修飾的方法無法訪問實體屬性,但是可以訪問類屬性,
~
運行結果:
<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
File "D:\pythonProject1\專題2.py", line 21, in <module>
b.eat()
File "D:\pythonProject1\專題2.py", line 14, in eat
print(self.name)
NameError: name 'self' is not defined
通過結果可以看出,從sleep()和eat() 列印的物件來看,sleep()傳遞的物件是創建的實體物件b,而被修飾器修飾的函式eat()傳遞的是類;從eat()訪問類的屬性和實體屬性來看,訪問類的屬性是沒有問題的,但訪問實體物件的屬性時就報錯了,
所以驗證了前面講的:被classmethod修飾的方法,與實體方法的區別是接收的第一個引數不是self,而是cls(當前類的具體型別),被修飾的方法無法訪問實體屬性,但是可以訪問類屬性,
今天的分享就到這里,歡迎大家在評論區留言交流!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/316641.html
標籤:python
