文章目錄
- 前言
- 一、函數
- 1.1 函數作為對象傳遞
- 1.2 函數作為參數傳遞
- 1.3 函數可嵌套
- 1.4 返回函數本身
- 二、裝飾器
- 2.1 基礎裝飾器
- 2.2 帶參裝飾器
- 2.3 裝飾器自定義參數
- 2.4 類裝飾器
- 2.5 裝飾器嵌套
- 三、裝飾器的應用
- 四、總結
前言
我的個人網站:https://www.gentlecp.com
python中有一個很經典的用法就是裝飾器,它用于在不修改原始函數的情況下,添加新的功能到原始函數中,但是這章內容比較難以理解,本文就從函數到裝飾器以及裝飾器在現實生產中的應用舉出樣例,希望能夠幫助大家更好地理解裝飾器到底有何用處
附:文章個人網站鏈接
一、函數
談裝飾器前先對函數要有一個深刻理解。在python中,萬物皆對象,函數也不例外,我們創建一個函數將其打印出來,看看結果:
def
func
(
message
)
:
print
(
'Here is func: {}'
.
format
(
message
)
)
print
(
func
)
可以看到返回值為一個內存地址,說明函數是對象,既然是對象,自然可以進行賦值傳遞,參數傳遞操作。
1.1 函數作為對象傳遞
看下面的例子:
def
func
(
message
)
:
print
(
'Here is func: {}'
.
format
(
message
)
)
func_object
=
func
func_object
(
'Hello world'
)
我們將func這個對象直接賦值給func_object,用func_object調用,結果成功輸出。說明函數可作為對象傳遞
1.2 函數作為參數傳遞
看下面的例子:
def
func_a
(
message
)
:
print
(
'Here is func_a:{}'
.
format
(
message
)
)
def
func_b
(
func
,
message
)
:
func
(
message
)
func_b
(
func_a
,
'Hello world'
)
我們將func_a作為func_b的參數傳入func_b中,并在func_b中調用該函數,打印輸出結果。說明函數可以作為參數傳遞。
1.3 函數可嵌套
這個大家應該都用過,就是在函數中有時候我們會重復利用一部分代碼,而這部分代碼在其他地方又不會用到,就可以將這部分整合成一個函數,然后進行調用,看下面的例子:
def
func_root
(
message
)
:
def
func_node
(
message
)
:
print
(
'Here is func_node:{}'
.
format
(
message
)
)
# 將func_node結果返回
return
func_node
(
message
)
func_root
(
'Hello World'
)
我們在func_root中又定義了func_node,并在func_root中返回func_node的處理結果。相當于在函數內部調用了內部的函數,說明函數可嵌套。
舉一個形象的例子:
老板(func_root)收到外包商一個項目(‘Hello World’)。
外包商:你幫我打印一下Hello World
老板接收原材料(‘Hello World’)。
老板:我不會啊,那什么,小C你幫我打印一下。
然后將原材料(‘Hello World’)扔給小C(= =)。
小C:好的老板,打印了Hello World,寫成了報告。
老板一看:哎喲,不錯哦。
然后就將結果return給了外包商。
1.4 返回函數本身
前面是內部函數處理了結果,外部函數將結果返回,因為函數本身是對象,自然也可以作為return返回,看下面這個例子:
def
func_root
(
)
:
def
func_node
(
message
)
:
print
(
'Here is func_node:{}'
.
format
(
message
)
)
# 將func_node本身返回
return
func_node
func_object
=
func_root
(
)
func_object
(
'Hello world'
)
同樣用上面的例子:
外包商(func_object):你幫我打印Hello World
老板(func_root):我不會啊,我把會打印的人叫來給你用吧,小C你過來一下
外包商:小C,你給我打印Hello World
小C(func_node):print(…)
在代碼中,func_object通過func_root這個橋梁,獲取到了func_node的地址,這時候它就可以通過()的形式調用func_node的功能。
二、裝飾器
2.1 基礎裝飾器
我們先看一個最基礎的裝飾器:
def
my_decorator
(
func
)
:
def
wrapper
(
)
:
print
(
"I'm wrapper in my_decorator"
)
func
(
)
return
wrapper
@my_decorator
def
hello
(
)
:
print
(
'hello world'
)
hello
(
)
其中@my_decorator稱為語法糖,其作用等價于
hello
=
my_decorator
(
hello
)
從結果可以看出,hello成功執行了自己的功能(打印hello world),裝飾器裝飾了hello函數,并在其基礎上添加了功能(打印I’m wrapper in my_decorator)。裝飾器一般有兩層函數,外層my_decorator用于@裝飾在其他函數上,它返回內層函數(wrapper)的地址,讓被裝飾函數可以直接獲取到該地址。
前面說過,有了函數地址,自然可以調用函數功能,所以wrapper可以看作被裝飾函數的加強版函數,在其內部必然要調用被裝飾函數,且添加一些想添加的代碼功能,這使得添加的功能和原始功能互不干擾。
舉個例子:
小C:我只會打印hello world,別的我不學,哼~
老板:什么?這么菜怎么完成任務?不行,我給你個神器,這個神器可以自動打印I’m wrapper in my_decorator,也不用你學什么了。但是你要把它戴到頭上。以后我布置打印任務的時候,我會發命令給這個神器,這個神器再提示你怎么做知道嗎?
小C:好的,老板!
老板:神器啊神器,開始打印吧~
神器:打印I’m wrapper in my_decorator完成,小C,打印hello world
小C:好嘞,打印hello world。
有人可能要問了,為什么不直接用一個函數wrapper,將hello傳入其中,然后調用呢?
注意!裝飾器的初衷是不影響原始函數的代碼和功能,也就是說。假設原始函數hello是一個接口,別人一直用hello作為接口調用,如果你用wrapper接收hello,那么接口的名稱就要改動成wrapper,外面的人并不知道這個,還是用hello調用,就會導致出錯!而用裝飾器的形式,你發現沒有,hello接口沒有變,但是新功能已經添加進去了。
2.2 帶參裝飾器
被裝飾函數難免會有參數傳入,如何將這些參數一并傳入到裝飾器中呢?看下面的代碼:
def
my_decorator
(
func
)
:
def
wrapper
(
*
args
,
**
kwargs
)
:
print
(
"I'm wrapper in my_decorator"
)
func
(
*
args
,
**
kwargs
)
return
wrapper
@my_decorator
def
hello
(
name
)
:
print
(
'hello world '
+
name
)
hello
(
'CP'
)
這里用到了 args,**kwargs,這樣就可以接收任意數量或類型的參數,關于 args,**kwargs網上有很多解釋的文章,這里不多贅述。可以看到成功地進行了參數傳遞。
2.3 裝飾器自定義參數
前面裝飾器一直是接收被裝飾函數的參數,那么如果裝飾器自己要定義參數呢?例如定義裝飾器參數num,用于指定裝飾器調用的次數,看下面的代碼:
def
repeat
(
num
)
:
def
my_decorator
(
func
)
:
def
wrapper
(
)
:
for
i
in
range
(
num
)
:
print
(
"I'm wrapper in my_decorator"
)
func
(
)
return
wrapper
return
my_decorator
@repeat
(
5
)
def
hello
(
)
:
print
(
'hello world'
)
hello
(
)
2.4 類裝飾器
類也可以作為裝飾器使用,它依賴于函數__call__,實際上,每次調用類的實例,函數__call__便執行一次。看下面的代碼:
class
CountClass
:
def
__init__
(
self
,
func
)
:
self
.
func
=
func
self
.
calls
=
0
def
__call__
(
self
,
*
args
,
**
kwargs
)
:
self
.
calls
+=
1
print
(
'calls: {}'
.
format
(
self
.
calls
)
)
return
self
.
func
(
*
args
,
**
kwargs
)
@CountClass
def
hello
(
)
:
print
(
"hello world"
)
hello
(
)
hello
(
)
本質上作用與函數裝飾器相同,根據自己的需要選擇用函數裝飾器還是類裝飾器
2.5 裝飾器嵌套
裝飾器說到底還是函數,因此裝飾器本身也可以被裝飾,看下面的例子:
import
functools
def
my_decorator1
(
func
)
:
def
wrapper
(
*
args
,
**
kwargs
)
:
print
(
'Here is decorator1'
)
func
(
*
args
,
**
kwargs
)
return
wrapper
def
my_decorator2
(
func
)
:
def
wrapper
(
*
args
,
**
kwargs
)
:
print
(
'Here is decorator2'
)
func
(
*
args
,
**
kwargs
)
return
wrapper
@my_decorator1
@my_decorator2
def
hello
(
message
)
:
print
(
message
)
hello
(
'hello world'
)
由結果可以看出來,執行的順序是?decorator1 -> decorator2 -> hello
三、裝飾器的應用
裝飾器的應用場景有很多,例如登錄驗證,日志記錄,合理性檢查等。下面以登錄驗證為例:假設一個網站有兩個功能,登錄和評論,而評論功能必須要先檢查用戶是否登錄,登錄才能使用否則調用評論會提示需要登錄,看下面代碼:
IS_LOGIN
=
False
# 全局變量作為是否登錄的標志
USER
,
PWD
=
'CP'
,
'123456'
# 假設只有一個用戶
def
require_login
(
func
)
:
def
wrapper
(
*
args
,
**
kwargs
)
:
if
IS_LOGIN
:
# 驗證通過,可以評論
func
(
*
args
,
**
kwargs
)
else
:
# 驗證不通過
print
(
'驗證失敗,您未登錄'
)
login
(
)
return
wrapper
# 登錄
def
login
(
)
:
global
IS_LOGIN
while
True
:
print
(
'請輸入登錄用戶名:'
)
user
=
input
(
)
print
(
'請輸入密碼:'
)
pwd
=
input
(
)
# 這里為了簡便沒有做密碼輸入加密處理,實際開發需要做
if
user
==
USER
and
pwd
==
PWD
:
print
(
'登錄成功'
)
IS_LOGIN
=
True
break
else
:
print
(
'用戶名或密碼輸入錯誤,請重新輸入'
)
# 評論功能
@require_login
def
comment
(
)
:
print
(
'歡迎使用評論功能,請輸入你要評論的內容:'
)
com
=
input
(
)
print
(
'你的評論是:{}'
.
format
(
com
)
)
comment
(
)
comment
(
)
運行結果:
我們定義兩個函數和一個裝飾器函數,login用于登錄,comment用于評論,comment被require_login裝飾器裝飾,在require_login裝飾器中會判斷全局變量IS_LOGIN來檢查用戶是否已經登錄,如果登錄則執行comment的功能,如果未登錄,則提示用戶進行登錄。
第一次調用comment()提示驗證失敗,您未登錄,并讓用戶進行登錄操作,登錄成功后,進入評論的核心功能,用戶可以進行評論。
通過上面的例子我們可以發現,comment還是那個comment,我們并沒有對其修改,但是讓其使用的時候多了一層驗證,而且這種方式在不改變接口的同時降低了代碼耦合性,使得程序員維護成本大大降低,所以是一種非常值得學習、掌握的方法。
四、總結
關于裝飾器的使用,我大概就總結了以上內容,希望能夠幫助到你更好地理解裝飾器,如果有任何疑問的地方,歡迎在評論區留言或私信給我,我會盡力解答~
建議:
看完文章后一定要自己動手嘗試一下才能更好地掌握,不要想當然以為自己看了就會了,動手才是將東西變成自己的的唯一途徑!!!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
