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

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