文章目錄
裝飾器的核心理念——在python中,一切皆對象,所以,
函數也是一個對象
!
1、裝飾器是什麼?有何好處?
- 定義
簡單來說,裝飾器
,是在不更改程序主體內容的前提下,對其進行功能擴展的解決方案。常用於權限驗證、數據校驗、日誌打印等需求。 - 優點
(1) 不用更改程序主體。這意味着主業務邏輯並不會受到影響,減少了修改成本及擴展風險。
(2) 可對多個有同樣需求的對象進行裝飾,提高了代碼複用性。
2、理解裝飾器的前提-高階函數與嵌套函數
裝飾器的本質是一個函數(類裝飾器也是通過__call__函數來實現)
,不過與一般函數不同的是,它是一個高階嵌套函數
,所以在真正理解裝飾器之前,得先明白什麼是高階函數
和嵌套函數
2.1 高階函數
如最開始所說,函數也是一個對象,這意味着可以將它賦給一個變量,而函數本身也可以接受變量作爲參數,所以所謂的高階函數,便是 將一個函數作爲另一個函數的參數的函數
。
文字有點繞,看個實例
#func:用於接收傳來的函數對象
def high_func(func):
print('我是一個高階函數,我叫:%s'%high_func.__name__)
print('我是傳來的函數對象,我叫:%s'%func.__name__)
def normal_func():
print('我是一個普通函數')
#查看normal_func的類型
print(type(normal_func))
#調用high_func函數,將normal_func函數作爲參數
high_func(normal_func)
輸出:
<class 'function'>
我是一個高階函數,我叫:high_func
我是傳來的函數對象,我叫:normal_func
由結果可知,normal_func
是一個函數對象,作爲參數賦給了變量func
,此時high_func
就成了一個高階函數。
2.2 嵌套函數
這個很好理解,就像穿衣服一樣的,一件套一件。程序中,就是一個函數中包含一個或多個函數
,可以不停嵌套,但建議不要超過3個。
實例如下:
#嵌套函數定義
def first():
print('我是第一層')
def second():
print('我是第二層')
def thirst():
print('我是第三層')
return thirst
return second
#調用,從外到內,層層調用
f = first()()()
輸出:
我是第一層
我是第二層
我是第三層
3、函數裝飾器
函數裝飾器可根據參數的需求分爲以下三種。
3.1 無參數
這種裝飾器最爲簡單,實例如下:
# 無參數裝飾器
def decorator1(func):
def wrapper():
print('我在被裝飾函數之前執行')
func()
print('我在被裝飾函數之後執行')
return wrapper
@decorator1 #調用裝飾器 @參數器函數名
def f():
print('我是被裝飾的函數')
輸出:
我在被裝飾函數之前執行
我是被裝飾的函數
我在被裝飾函數之後執行
如上,我們定義了一個簡單的裝飾器,裝飾器decorator1與被裝飾的函數f都無參數
。它的功能是在被裝飾的函數f
執行前後各打印一句話。
裝飾原理: 上面代碼中,使用了@
語法糖來調用裝飾器。其原理是將被裝飾的函數f
作爲參數,傳入裝飾器函數decorate1
中,之後將返回的wrapper
函數重新賦給f
,即 f = decorator1(f)
3.2 被裝飾的函數帶參數
# 被裝飾函數帶有參數的裝飾器
def decorator2(func):
def wrapper(*args, **kwargs):
print('我是被裝飾函數帶有參數的裝飾器')
return func(*args, **kwargs) #因爲原函數f有返回值,所以此處要return
return wrapper
@decorator2
def f(name):
print('我是被裝飾的函數,我有參數name,值爲:%s' % name)
return name
print('返回值:',f(name='jxc'))
print('被裝飾的函數是%s' % f.__name__)
輸出:
我是被裝飾函數帶有參數的裝飾器
我是被裝飾的函數,我有參數name,值爲:jxc
被裝飾的函數是wrapper
如上,被裝飾的函數f
帶有參數name
,所以裝飾器的內部嵌套了一個函數wrapper
來接受參數。
但此時出現了一個問題,被裝飾的函數明明是f
,怎麼打印出來變成了wrapper
呢?很簡單,注意這一句 return wrapper
,將內部的wrapper函數返回,再賦給了f,所以此時的f.__name__
變成了wrapper。
解決辦法:使用functools 中的wraps裝飾器 ,改寫如下:
from functools import wraps
#被裝飾函數帶有參數的裝飾器
def decorator2(func):
@wraps(func) #使用wraps裝飾器保存原函數信息
def wrapper(*args,**kwargs):
print('我是被裝飾函數帶有參數的裝飾器')
return func(*args, **kwargs)
return wrapper
@decorator2
def f(name):
print('我是被裝飾的函數,我有參數name,值爲:%s'%name)
return name
f(name='jxc')
print('被裝飾的函數是%s'%f.__name__)
輸出:
我是被裝飾函數帶有參數的裝飾器
我是被裝飾的函數,我有參數name,值爲:jxc
被裝飾的函數是f
3.3 裝飾器帶有參數
通過上面我們知道,兩層嵌套的裝飾器只能接收被裝飾函數的參數。所以要想接收裝飾器傳來的參數,那就得再加一層
。
在3.2的基礎上加個需求,根據成績分數判斷是否及格
#裝飾器帶有參數的裝飾器
def decorator3(grade):
def get_func(func):
@wraps(func)
def wrapper(*args,**kwargs):
print('我是裝飾器帶有參數的裝飾器')
words = '恭喜你,及格了' if grade>=60 else '沒及格,下次努力'
print('本次考了%d分'%grade,words)
return func(*args, **kwargs)
return wrapper
return get_func
@decorator3(grade=60)
def f(name):
print('我是被裝飾的函數,我有參數name,值爲:%s'%name)
f(name='jxc')
print('被裝飾的函數是%s'%f.__name__)
輸出:
我是裝飾器帶有參數的裝飾器
本次考了60分 恭喜你,及格了
我是被裝飾的函數,我有參數name,值爲:jxc
被裝飾的函數是f
可見,此時的裝飾器共有三層,最外層用來接收裝飾器參數,中間層接收被裝飾函數對象,最裏層接收被裝飾函數的參數。
4、類裝飾器
顧名思義,類裝飾器是通過類的形式(本質上還是通過__call__函數,將類變爲一個函數來實現
)來進行功能擴展的,相比於函數裝飾器而言,它具有高內聚
、可繼承
等特點。
實例如下:
class decoratorTest():
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print('我是一個類裝飾器,現在執行被裝飾的函數:%s'%self.func.__name__)
print('傳入的參數:',kwargs)
return self.func(*args,**kwargs)
@decoratorTest
def f(name):
print('我是被裝飾的函數,我有參數name,值爲:%s'%name)
f(name='jxc')
輸出:
我是一個類裝飾器,現在執行被裝飾的函數:f
傳入的參數: {'name': 'jxc'}
我是被裝飾的函數,我有參數name,值爲:jxc
__init__
接收被裝飾的函數對象,__call__
進行邏輯處理,類裝飾器這裏不作深入研究。
5、多個裝飾器的執行順序
一個函數可以被多個裝飾器同時裝飾,實例如下:
#裝飾器d1
def d1(func):
def warpper1():
print('洗臉')
func()
return warpper1
#裝飾器d2
def d2(func):
def warpper2():
print('刷牙')
func()
return warpper2
#裝飾器d3
def d3(func):
def warpper3():
print('起牀')
func()
return warpper3
@d3
@d2
@d1
def f():
print('上班去嘍')
f()
輸出:
起牀
刷牙
洗臉
上班去嘍
如上,使用了d1、d2、d3
三個裝飾器同時對函數f
進行裝飾。
- 裝飾順序:
d1—>d2—>d3
- 執行順序:
d3—>d2—>d1—>f
可見,執行順序與裝飾順序是相反的,這是爲什麼呢?根據3.1提到的的裝飾原理可以得到下列式子:f = d3(d2(d1(f)))
裝飾步驟:
(1)將原函數f
作爲參數傳給d1
,此時d1中的func等於f
(2)再將d1
作爲參數傳給d2
,此時d2中的func等於d1
(3)再將d2
作爲參數傳給d3
,此時d3中的func等於d2
(4)最後將d3
的返回函數wrapper3
賦給原函數f
執行步驟:
(1)執行f()
之後,先執行d3的warpper3
,打印起牀,之後調用d2
(2)d2的warpper2
打印刷牙,之後調用d1
(3) d1的wrapper1
打印洗臉,最後執行原函數f
(4) f
打印 上班去嘍
小結: 需要先執行的裝飾器放上面!
按照自己的理解寫的,內容較多,有些地方的表述可能比較繞或不準確,歡迎留言討論~
參考文章:
[1] 如何理解Python裝飾器?- 知乎
[2] Python 裝飾器執行順序迷思
博主其他系列文章:
[1] 【python實用特性】-切片
[2] 【python實用特性】- 迭代、可迭代對象、迭代器
[5] Python如何爬取動態網頁數據