溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python中裝飾器的基本功能有哪些

發(fā)布時(shí)間:2021-10-22 10:04:39 來(lái)源:億速云 閱讀:140 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“Python中裝飾器的基本功能有哪些”,在日常操作中,相信很多人在Python中裝飾器的基本功能有哪些問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Python中裝飾器的基本功能有哪些”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

    前言

    在 python 中,裝飾器由于是 python 語(yǔ)言自帶的一個(gè)功能,因此,對(duì)于其實(shí)現(xiàn)以及其用法就會(huì)感到比較奇怪,這里我記錄一下對(duì)它的理解,加深自己的印象。

    什么是裝飾器

    對(duì)于什么是裝飾器,我們其實(shí)應(yīng)該知道為什么會(huì)存在裝飾器。

    裝飾器是 python 引入的一個(gè)非常有意思的功能,它主要用于解決想要在原有函數(shù)或類的基礎(chǔ)上進(jìn)行功能擴(kuò)展,但又不會(huì)破壞這個(gè)函數(shù)本身的功能。并且我們可以方便的添加和去除這一部分?jǐn)U展的功能的需求。

    例如:當(dāng)你在調(diào)試代碼的時(shí)候,你想要給一些功能函數(shù)添加打印調(diào)試信息。但是,這個(gè)功能只在我們調(diào)試的時(shí)候使用,正式發(fā)布的時(shí)候是不需要的。如果此時(shí),你在函數(shù)內(nèi)部修改,由于有多個(gè)函數(shù)都需要添加相同的或類似的信息,那么,你就需要逐一修改,這個(gè)就有點(diǎn)麻煩了,此時(shí)我們就可以使用裝飾器來(lái)解決這一問(wèn)題。

    在解釋如何使用裝飾器之前,我們需要先把和裝飾器有關(guān)的基本概念給講一下。

    Python 函數(shù)的基本特性

    函數(shù)名的本質(zhì):

    在 python 中,一切皆是對(duì)象,也就是說(shuō),我們定義的變量和函數(shù)都是一個(gè)對(duì)象。而是對(duì)象就意味著我們可以獲得這個(gè)對(duì)象的屬性,例如函數(shù)對(duì)象有一個(gè) __name__ 的屬性:

    def function(): #定義一個(gè)函數(shù)
        print('this is a function !')
    
    function()
    print(function) #打印函數(shù)名的地址
    print(function.__name__) #打印函數(shù)名
    
    a = function #把函數(shù)賦給一個(gè)變量
    a()
    print(a) #打印 a 的地址
    print(a.__name__) #再次打印函數(shù)名

    打印結(jié)果:

    this is a function !
    <function function at 0x0000029F83C17F70>
    function
    this is a function !
    <function function at 0x0000029F83C17F70>
    function

    由打印可以看出,我們的函數(shù)名在賦給另一個(gè)變量的時(shí)候,其函數(shù)地址和函數(shù)屬性中的函數(shù)名是沒(méi)有變化的。也就是說(shuō),當(dāng)我們?cè)诙x函數(shù)的時(shí)候,我們的函數(shù)名和普通的變量是一樣的,唯一的不同就是,我們的函數(shù)名會(huì)指向一個(gè)內(nèi)存空間,而這片空間內(nèi)保存的是一個(gè)函數(shù)體的內(nèi)容。

    但是,當(dāng)我們把這個(gè)函數(shù)名賦值給其他變量的時(shí)候,這個(gè)函數(shù)名就會(huì)把它執(zhí)行的內(nèi)存空間的地址賦值給另一個(gè)變量,因此,另一個(gè)變量也就成為了一個(gè)函數(shù)了。

    這里我們已經(jīng)能夠注意到了,函數(shù)名如果不加 () 那么它和普通的變量一樣,而加了 () 之后,它就會(huì)去執(zhí)行我們的函數(shù)內(nèi)容。

    這里我們把試著刪除我們定義時(shí)候使用的函數(shù)名:

    del function #刪除 function 函數(shù)
    a() #執(zhí)行 a()
    print(a) #打印出 a 指向的地址
    print(a.__name__) #打印a的函數(shù)名
    
    function()
    print(function)
    print(function.__name__)

    查看打?。?/p>

    this is a function !
    <function function at 0x000002258DC17F70>
    function
    NameError: name 'function' is not defined

    可以看到,我們的 function() 函數(shù)名提示沒(méi)有定義,但是我們的 a() 函數(shù)卻可以正常的打印出來(lái)。這里的 del 其實(shí)就是把我們的 function 這個(gè)函數(shù)名的指針給指向的函數(shù)地址給刪去了,此時(shí)它變成立一個(gè)真正的未定義的變量了。

    將函數(shù)作為變量使用:

    既然函數(shù)名和普通的變量可以相互賦值,那就說(shuō)明,我們也可以像使用普通變量一樣使用函數(shù)名了。

    在函數(shù)中定義函數(shù):

    我們可以像定義普通變量一樣,在一個(gè)函數(shù)中定義另一個(gè)函數(shù):

    def function1():
        print('this is function 1')
        def function2():
            print('this is function 2 !')
            return 0
        function2()
        return 0
    
    function1()
    function2()

    打印如下:

    this is function 1
    this is function 2 !
    NameError: name 'function2' is not defined

    可以看到,我們?cè)?function1 中定義了一個(gè) function2 函數(shù),而且在 function1 中使用了 function2 這個(gè)函數(shù)。但是,當(dāng)我們?cè)谕饷媸褂?function2 這個(gè)函數(shù)的時(shí)候,卻打印了該函數(shù)未定義。這里說(shuō)明,函數(shù)內(nèi)定義的函數(shù)的作用域也僅限于函數(shù)內(nèi)部,和我們的局部變量是一樣的。

    但是,當(dāng)我們把函數(shù)作為返回值的時(shí)候,這個(gè)情況就不一樣了,這里參考我上一篇文章:Python中的閉包中的變量作用域問(wèn)題

    在函數(shù)中返回函數(shù)名:

    既然我們可以在一個(gè)函數(shù)中定義另一個(gè)函數(shù),那么也就可以在函數(shù)中返回另一個(gè)函數(shù):

    def function1():
        print('this is function 1')
        def function2():
            print('this is function 2 !')
            return 0
        function2() #在函數(shù)內(nèi)部使用該函數(shù)
        return function2 #返回該函數(shù)的函數(shù)名
    
    a = function1() #把函數(shù)名返回給一個(gè)變量
    a()

    打印如下:

    this is function 1
    this is function 2 !
    this is function 2 !

    這里可以看到,我們的這個(gè)在函數(shù) function1 中定義并返回了函數(shù) function2 并在外部使用一個(gè)變量來(lái)接收 funciton1 的返回值。由此可以看出,函數(shù)名和變量的使用方式差別不大。

    注意: 雖然我們說(shuō)的時(shí)候會(huì)說(shuō)在一個(gè)函數(shù)中返回另一個(gè)函數(shù),但是,實(shí)際上,我們返回的只是這個(gè)函數(shù)的函數(shù)名(不帶括號(hào)'()‘)。

    把函數(shù)名作為參數(shù)使用:

    def hello():
        print("hello")
    
    def function1(func): #接收一個(gè)參數(shù)
        print('before call hello !')
        func()
        print('after call hello !')
        #function2() #在函數(shù)內(nèi)部使用該函數(shù)
    
    function1(hello) #把 hello 作為參數(shù)傳遞進(jìn)去

    打印如下:

    before call hello !
    hello
    after call hello !

    由打印可以知道,我們?cè)诤瘮?shù) function1 中定義的接收參數(shù) func 我們?cè)诙x的時(shí)候并沒(méi)有采用什么特殊的方式,而是和普通參數(shù)一樣定義。之后,在外部調(diào)用 function1 的使用,把函數(shù)名 hello 當(dāng)作參數(shù)傳遞進(jìn)去了。隨后,我們運(yùn)行 function1 并在 function1 中成功調(diào)用了 hello 函數(shù)。

    進(jìn)一步實(shí)現(xiàn)裝飾器

    現(xiàn)在,讓我們?cè)僦匦驴匆幌率裁词茄b飾器,我們?cè)谏厦娴陌押瘮?shù)名作為參數(shù)使用時(shí),已經(jīng)實(shí)現(xiàn)了一個(gè)和裝飾器功能類似的函數(shù)了。假如我們的 hello() 函數(shù)是我們的功能函數(shù),而 function1 作為我們的裝飾器,那么,我們成功實(shí)現(xiàn)了在不改變 hello() 函數(shù)的基礎(chǔ)上,通過(guò)把它作為參數(shù)使用而增加了其他的打印內(nèi)容。

    雖然我們上面實(shí)現(xiàn)了一個(gè)類似裝飾器的功能,但是,我們可以看到,使用這個(gè)的時(shí)候我們需要每次都給 function1 傳入一個(gè)函數(shù),這樣使用就很麻煩了。下面我們改造一下這個(gè)函數(shù):

    def decorator(func): #裝飾器函數(shù),用于接收一個(gè)函數(shù)參數(shù)
        def wrapper(): #定義一個(gè)內(nèi)函數(shù) wrapper 
            print('before call hello !')
            func() 
            print('after call hello !')
        return wrapper #把內(nèi)函數(shù)做未返回值
    
    def hello():
        print("hello")
        
    hello = decorator(hello) #重新定義一個(gè)函數(shù) hello1
    hello() #執(zhí)行 hello

    打印如下:

    before call hello !
    hello
    after call hello !

    通過(guò)上面的打印可以看到,我們新更改的這個(gè)函數(shù)可以實(shí)現(xiàn)和上面的函數(shù)一樣的功能。但是,我們這里在使用它的時(shí)候比之前要簡(jiǎn)單一些,因?yàn)槲覀兛梢灾苯邮褂门f的函數(shù)名來(lái)使用新的功能 (這里我們相當(dāng)于給函數(shù)名 hello 賦值了一個(gè)新的函數(shù)wrapper),當(dāng)我們想要使用舊函數(shù)時(shí),只需要把 hello=function(hello) 這行內(nèi)容給注釋掉就可以了。

    使用Python裝飾器語(yǔ)句:

    簡(jiǎn)單裝飾器

    上面的我們已經(jīng)實(shí)現(xiàn)了一個(gè)裝飾器的功能,下面我們使用 Python 中自帶的裝飾器來(lái)測(cè)試一下:

    def decorator(func): #裝飾器函數(shù),用于接收一個(gè)函數(shù)參數(shù)
        def wrapper(): #定義一個(gè)內(nèi)函數(shù) wrapper 
            print('before call hello !')
            func() 
            print('after call hello !')
        return wrapper #把內(nèi)函數(shù)做為返回值
    
    @decorator # '@' 是系統(tǒng)自帶的裝飾器語(yǔ)法糖
    def hello():
        print('hello')
    
    hello()

    打印如下:

    before call hello !
    hello
    after call hello !

    可以看到,我們使用系統(tǒng)自帶得裝飾器語(yǔ)法實(shí)現(xiàn)了和我們上面得函數(shù)一樣得功能。

    這里我們可以看到,他們兩個(gè)唯一得不同就是使用了 @decorator這個(gè)符號(hào)來(lái)代替了 hello=decorator(hello) 這個(gè)賦值語(yǔ)句。

    到這里,其實(shí)我們基本上就已經(jīng)明白了python所謂得裝飾器的原理和實(shí)際用法了,但是,這里我們還有一個(gè)問(wèn)題,那就是這種方法會(huì)改變我們的函數(shù)的屬性嗎?

    我們測(cè)試一下:

    print(hello.__name__)

    打印如下:

    wrapper

    很明顯,我們的原函數(shù)的屬性中的函數(shù)名被更改了,其實(shí)通過(guò)上面自己的實(shí)現(xiàn),我們可以發(fā)現(xiàn),我們使用裝飾器語(yǔ)法其實(shí)就是新建了一個(gè)函數(shù)名,然后用它去接收裝飾器函數(shù)的返回函數(shù)名,這樣,該該函數(shù)肯定還是繼承了裝飾器返回函數(shù)的函數(shù)名了。

    為了解決這個(gè)問(wèn)題,我們可以使用如下方法:

    import functools 
    
    def function(func): #接收一個(gè)參數(shù)
        @functools.wraps(func)
        def wrapper(): #定義一個(gè)內(nèi)函數(shù) wrapper 
            print('before call hello !')
            func() 
            print('after call hello !')
        return wrapper #把內(nèi)函數(shù)做為返回值
    
    @function # '@' 是系統(tǒng)自帶的裝飾器語(yǔ)法糖
    def hello():
        print('hello')
    
    hello()
    print(hello.__name__)

    打印如下:

    before call hello !
    hello
    after call hello !
    hello

    通過(guò)我們使用系統(tǒng)模塊,我們解決了這一問(wèn)題。

    帶參數(shù)裝飾器:

    上面我們展示了裝飾器的基礎(chǔ)用法,但是,我們可以發(fā)現(xiàn)一個(gè)問(wèn)題,那就是這個(gè)裝飾器只能用于打印一類的基本操作,有時(shí)我們需要在裝飾器函數(shù)內(nèi)傳參,且需要在多個(gè)函數(shù)中使用同一個(gè)裝飾器函數(shù),如果單純使用上面的方法就不太容易操作了。

    下面我們展示一種給裝飾器傳參的操作方法:

    import functools
    
    def logging(level):#裝飾器接收參數(shù)函數(shù)
        def decorator(func): #裝飾器函數(shù),用于接收一個(gè)函數(shù)
            @functools.wraps(func)
            def wrapper(*args, **kwargs): #定義一個(gè)內(nèi)函數(shù) wrapper 
                if level == 'warn':
                    print('warn: before call %s !' %func.__name__)
                    func() 
                    print('warn: after call %s !' %func.__name__)
                if level == 'error':
                    print('error: before call %s !' %func.__name__)
                    func() 
                    print('error: after call %s !' %func.__name__)
            return wrapper #把內(nèi)函數(shù)做為返回值
        return decorator
    
    @logging(level='warn') # '@' 是系統(tǒng)自帶的裝飾器語(yǔ)法糖
    def hello():
        print('hello')
    
    @logging(level='error')
    def function1():
        print('function1')
    
    hello()
    function1()
    print(hello.__name__)
    print(function1.__name__)

    打印如下:

    warn: before call hello !
    hello
    warn: after call hello !
    error: before call function1 !
    function1
    error: after call function1 !
    hello
    function1

    可以看到,我們?cè)趦蓚€(gè)函數(shù)中使用了一個(gè)裝飾器語(yǔ)法,而且給這個(gè)裝飾器分別傳了不同的參數(shù),這個(gè)才比較符號(hào)我們實(shí)際可能會(huì)用到的情況。

    這里第一次看可能感覺(jué)有點(diǎn)復(fù)雜,而且我們?cè)谶@里也使用了多層函數(shù)嵌套,每層都傳不同的參數(shù)。這里我來(lái)仔細(xì)拆分一下這個(gè)函數(shù):

    首先我們知道:

    @logging
    def hello():
         print('hello')
    #等價(jià)于
    logging(hello)

    因此:

    @logging(level='warn')
    def hello():
         print('hello')
    #等價(jià)于
    logging(hello)(level='warn')

    下面我們繼續(xù)拆解 logging(hello)(level=‘warn') 這句話:

    logging(hello)(level='warn')

    由于
     logging(hello) 返回 decorator
    于是
     logging(hello)(level='warn')
        等價(jià)于
        decorator(level='warn')

     decorator 返回 wrapper
        因此
     這里其實(shí)就到了我們最上面的簡(jiǎn)單裝飾器了

    到這里我們就明白了我們的裝飾器傳參是怎么回事了。

    裝飾器類:

    由于我們?cè)?python 中會(huì)經(jīng)常使用類來(lái)對(duì)某一功能進(jìn)行封裝,這樣,當(dāng)我們?cè)谑褂媚骋还δ艿臅r(shí)候就更加靈活且方便了。

    因此,我們的 python 也給我們提供了實(shí)現(xiàn)裝飾器類的使用方法:

    class Logging(object):
        def __init__(self, func):
            self._func = func
    
        def __call__(self):
            print('class: before call %s !' %self._func.__name__)
            self._func() 
            print('class: after call %s !' %self._func.__name__)
    
    @Logging
    def hello():
        print('Hello')
    
    hello()

    打印如下:

    class: before call hello !
    Hello
    class: after call hello !

    可以看到,我們的類裝飾器的用法和函數(shù)類似,只是在定義裝飾器函數(shù)的時(shí)候,把函數(shù)的實(shí)現(xiàn)變成了類方法的實(shí)現(xiàn)方式。

    除了這種最基本的的使用方式,我們其實(shí)也可以給類裝飾器傳參:

    class Logging(object):
        def __init__(self, level='INFO'):
            self._level = level
    
        def __call__(self, func):
            def wrapper(*args, **kwargs):
                if self._level == 'WARN':
                    print('class: warn before call %s !' %func.__name__)
                    func() 
                    print('class: warn after call %s !' %func.__name__)
            return wrapper
    
    @Logging(level='WARN')
    def hello():
        print('Hello')
    
    hello()

    打印如下:

    class: warn before call hello !
    Hello
    class: warn after call hello !

    這里傳參方式和上面直接在類中的 __call__ 中定義函數(shù)有些不一樣,這里需要記住兩點(diǎn):

    __init__:不再接收被裝飾函數(shù),而是接收傳入?yún)?shù);
    __call__:接收被裝飾函數(shù),實(shí)現(xiàn)裝飾邏輯

    這里就不對(duì)這個(gè)類方法進(jìn)行深入解析了。

    到此,關(guān)于“Python中裝飾器的基本功能有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI