Python-裝飾器

說到裝飾器,很明顯就是用來裝飾的,既然是要裝飾,那肯定是在保留原有的基礎(chǔ)上再添加一些東西作為裝飾,這就是我對(duì)裝飾器最直白的理解。

那么如何去學(xué)習(xí)這個(gè)裝飾器呢?這個(gè)裝飾器又是咋回事?

裝飾器的幾個(gè)特點(diǎn)


首先我們需要先記住裝飾器的幾個(gè)特點(diǎn):

  • 裝飾器本質(zhì)上也是一個(gè)函數(shù) ,而這個(gè)函數(shù)的作用就是給其他函數(shù)添加一些其他功能作為裝飾。
  • 裝飾器不能去修改原函數(shù)的源代碼,只能是增加修飾
  • 裝飾器不能去修改原函數(shù)的調(diào)用方式 ,比如函數(shù)fun(),調(diào)用方式就是fun(),不能改變。

怎么實(shí)現(xiàn)裝飾器


裝飾器如果往簡(jiǎn)單的說就是以前學(xué)習(xí)的幾個(gè)知識(shí)點(diǎn)結(jié)合組合在一起。

裝飾器= 高階函數(shù)+嵌套函數(shù)

高階函數(shù)


  • 將函數(shù)名作為參數(shù)傳遞給另一個(gè)函數(shù)

    在python里,函數(shù)也是可以像變量那樣直接賦值給另一個(gè)變量的,這樣子就可以把函數(shù)名作為實(shí)參傳給另一個(gè)函數(shù)了。(依據(jù)這個(gè)就可以保證裝飾器特點(diǎn)之一:不修改原函數(shù)的源代碼,只添加功能)

    def func():
        print("Hello I am func")
        
    def main(var):
        print("Before var()")
        var()
        print("End var()")
        
    #把func作為參數(shù)傳給main
    main(func)
    # 這樣子就給func()函數(shù)執(zhí)行前和執(zhí)行后都添加了一句打印,是不是沒有修改了源碼,只添加了功能?
    
  • 返回值包含函數(shù)名

    函數(shù)名字可以作為參數(shù)傳遞,那么自然函數(shù)名也可以作為返回值了。

    在上面的例子中,雖然沒有修改源碼就在函數(shù)執(zhí)行前后添加了一句打印,但是這時(shí)候函數(shù)的調(diào)用方式變?yōu)榱?strong>main(func) ,而原函數(shù)的調(diào)用方式應(yīng)該是func() ,這就與裝飾器的另一個(gè)特點(diǎn):不能修改原函數(shù)的調(diào)用方式?jīng)_突了,解決方式就是函數(shù)名作為返回值。

    def func():
        print("Hello I am func")
        
    def main(var):
        print(var)
        return var
    
    print(main(func)) #執(zhí)行結(jié)果:會(huì)先打印func的內(nèi)存地址,然后因?yàn)閞eturn的也是func,所以也會(huì)打印出func的內(nèi)存地址,也就是會(huì)打印兩次func的地址
    
    #也可以把返回值賦值給另一個(gè)變量
    test = main(func)  
    test()  #等同于func()
    
    #為了不改變調(diào)用方式,我可以這么做:
    func = main(func)
    func()  #這樣子就跟直接調(diào)用func(),看起來一樣了,調(diào)用方式?jīng)]有改變
    

嵌套函數(shù)


嵌套函數(shù),就是之前講過的內(nèi)部函數(shù),在函數(shù)內(nèi)部定義另一個(gè)函數(shù),這里就當(dāng)做溫習(xí)下,這里注意內(nèi)部函數(shù)調(diào)用和沒調(diào)用的差別,整體理解不算難。

# 內(nèi)部函數(shù)只定義,沒有調(diào)用
def out():
    print("I am out")
    def _in():
        print("I am _in")
       
# 只調(diào)用out()
out()   # 只會(huì)執(zhí)行out(),內(nèi)部函數(shù)_in()不會(huì)執(zhí)行,因?yàn)閮?nèi)部只定義并沒有調(diào)用

# 內(nèi)部函數(shù)即定義又調(diào)用
def out1():
    print("I am out1")
    def _in1():
        print("I am _in1")
    return _in1

func = out1();
func()

裝飾器


1.有了高階函數(shù)和嵌套函數(shù)的基礎(chǔ),對(duì)于實(shí)現(xiàn)一個(gè)裝飾器就顯得比較好辦了,從上面例子看好像高階函數(shù)就能實(shí)現(xiàn)了裝飾器的兩個(gè)特點(diǎn),但其調(diào)用方式太繞,而高階函數(shù)又恰好能解決這個(gè),兩者結(jié)合就能實(shí)現(xiàn)裝飾器了。

# 定義一個(gè)裝飾器
def dec(func):
    # 內(nèi)部定義函數(shù)
    def _in():
        print("Before func")
        func()
        print("End func")
    return _in

# 定義一個(gè)原函數(shù)
def test():
    print("I am test()")
    
test()   # 最初的調(diào)用
test = dec(test) # 把返回值賦值給test,顯得調(diào)用方式并沒有變化
test()

上面例子dec就是一個(gè)裝飾器,但是我們?cè)谡{(diào)用的時(shí)候總是需要把返回值賦值給原函數(shù)名的變量,這樣略顯麻煩,在python里,有一個(gè)裝飾器的語法糖,就是在定義需要裝飾的函數(shù)前面用@ + 裝飾器名字,比如上例子中可以改為:

# 定義一個(gè)裝飾器
def dec(func):
    # 內(nèi)部定義函數(shù)
    def _in():
        print("Before func")
        func()
        print("End func")
    return _in

# 定義一個(gè)原函數(shù)
@dec
def test():
    print("I am test()")
    
test()

2.有沒有發(fā)現(xiàn)上面的例子中,原函數(shù)都是沒有帶參數(shù)的?下面說說帶參數(shù)的原函數(shù),裝飾器怎么寫。

說下思路:在裝飾器里,調(diào)用原函數(shù)的地方是在內(nèi)部函數(shù)里,return返回的也是內(nèi)部函數(shù)的地址,那么執(zhí)行的時(shí)候也是執(zhí)行了內(nèi)部函數(shù),所以如果原函數(shù)有參數(shù),那么內(nèi)部函數(shù)也應(yīng)該要相應(yīng)跟著參數(shù)才可以,否則一定會(huì)出錯(cuò),參數(shù)不匹配了。

# 函數(shù)參數(shù)個(gè)數(shù)是有限的
def dec(func):
    def _in(arg):
        print("Before func")
        func(arg)
        print("End func")
    return _in

@dec
def test(a):
    print("the arg is %d" %a)
    
test(4)

# 參數(shù)個(gè)數(shù)不限 這里就需要用到 *args **kwargs 了
def dec1(func1):
    def _in1(*args,**kwargs):
        print("Before func1")
        func1(*args,**kwargs)
        print("End func1")
    return _in1

@dec1
def test1(name,arg):
    print("The name is %s,arg is %d" %(name,arg))
   
test1("Test",12)

注意,如果一個(gè)原函數(shù)中,有多個(gè)裝飾器,那么裝飾器的執(zhí)行順序是怎樣的呢?

答案是從靠近函數(shù)頭的開始執(zhí)行,依次向上,比如:

@dec2

@dec1

def func():

? pass

執(zhí)行順序是從里到外,依次向上,等效于func = dec2(dec1(func))

3.原函數(shù)可以有參數(shù),有沒有想過裝飾器也可以有參數(shù)的?很簡(jiǎn)單嘛,本文開頭就說了裝飾器本質(zhì)就是函數(shù),那它肯定也可以有函數(shù)啦!裝飾器帶參數(shù)的做法,就是再多嵌套一層函數(shù)

# 不帶參數(shù)的裝飾器
def dec(func):
    def _in(*args,**kwargs):
        print("I am _in")
        func(*args,**kwargs)
    return _in
   
# 帶參數(shù)的裝飾器
def dec1(flag = 0):
    def real_dec(func):
        def _in(*args,**kwargs):
            if flag == 0:
                print("Run func")
                func(*args,**kwargs)
            else:
                print("No Run func")
        return _in
    return real_dec

@dec
def test():
    print("I am test")
    
@dec1(1)
def test1():
    print("I am test1")

test()
print("-------------")
test1()

注意:帶參數(shù)的裝飾器,在裝飾函數(shù)的時(shí)候要帶上(),比如上面的@dec1()

另外,除了函數(shù)帶參數(shù),函數(shù)還有返回值,在裝飾器里也是一樣的,原函數(shù)需要返回值,那么在內(nèi)部函數(shù)里就需要return。

4.原函數(shù)帶有return的情況

def dec(func):
    def _in(*args,**kwargs):
        print("I am _in")
        ret = func(*args,**kwargs)
        print("ret = %d" %(ret))
        return ret
    return _in

@dec
def test(a,b): #兩個(gè)數(shù)相加
    return a+b

test(3,4)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載來自:http://blog.csdn.net/u013471155 在學(xué)習(xí)Python的過程中,我相信有很多...
    JM68閱讀 677評(píng)論 3 9
  • 每個(gè)人都有的內(nèi)褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風(fēng)御寒,咋辦?我們想到的一個(gè)辦法就是把內(nèi)褲改造一下,...
    chen_000閱讀 1,403評(píng)論 0 3
  • 呵呵!作為一名教python的老師,我發(fā)現(xiàn)學(xué)生們基本上一開始很難搞定python的裝飾器,也許因?yàn)檠b飾器確實(shí)很難懂...
    TypingQuietly閱讀 20,314評(píng)論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中,函數(shù)通過...
    DraculaWong閱讀 588評(píng)論 0 3
  • 《暗殺》和《教父》里的兩場(chǎng)槍殺戲。 A. 2015年口碑頗佳的韓影《暗殺》里,李政宰飾演一個(gè)賣友求榮的叛徒廉碩晉,...
    親愛的園長(zhǎng)閱讀 422評(píng)論 1 2

友情鏈接更多精彩內(nèi)容