python-裝飾器

前言

行為:裝飾器(decorator)可以對(duì)一個(gè)函數(shù)、方法或者類(lèi)進(jìn)行“加工”,相當(dāng)于在封裝。
目的:抽象化代碼,利用函數(shù)是一等公民的特性來(lái)復(fù)用代碼,人生苦短,趕緊偷懶。
須知:越高抽象速度越慢,畢竟函數(shù)的跳轉(zhuǎn)也需要時(shí)間。


本質(zhì)和語(yǔ)法糖

其本質(zhì)是利用函數(shù)作為 python 中的一等公民的特性,可以作為變量來(lái)使用,這時(shí)作為變量時(shí)其實(shí)傳遞的是其函數(shù)的引用(地址)。

也就是說(shuō),只要是將函數(shù)當(dāng)成一等公民的編程語(yǔ)言,其實(shí)都可以寫(xiě)裝飾器。

裝飾器:

def deco(func):
    def __funny():
        print("2")
        func() # 核心
        print("3333")
        return None # 注意返回 None
    return __funny

def foo():
    print("foo")

f = deco(foo) # 核心
n = foo()
print(n)
image.png

很直白的,首先將 foo 的引用傳遞給 deco 函數(shù),然后 deco 函數(shù)將會(huì)返回一個(gè) __funny() 的引用,所以最后 f 其實(shí)獲取了 “__funny() 的引用”,也就是說(shuō) f() 就是 __funny(),在之后 n = f() 的調(diào)用將會(huì)得到 f() 的返回值,這里我返回了一個(gè) None 值。

利用語(yǔ)法糖 @:

def deco(func):
    def __funny():
        print("2")
        func() # 核心
        print("3333")
        return None # 注意返回 None
    return __funny

@deco
def foo():
    print("foo")

n = f()
print(n)
image.png

語(yǔ)法糖其實(shí)就是進(jìn)行了 f = deco(foo) 的操作,一句話就是懶。

注意裝飾器內(nèi)部的 self 用于指明這是哪一個(gè)類(lèi)的實(shí)例,所以j即使寫(xiě)在類(lèi)外部也不能省略,因?yàn)閷?shí)際運(yùn)行會(huì)被暴露出來(lái)啦。


與默認(rèn)參數(shù)和關(guān)鍵字參數(shù)的友好會(huì)晤

與默認(rèn)參數(shù)和關(guān)鍵參數(shù)的友好結(jié)合,將會(huì)大大的提高靈活性,能夠變得更加的懶惰。

def deco(func):
    def inner(self, *argv, **kwargv):
        print("2")
        r = func(self, *argv, **kwargv)
        print("3333")
        return None
    return inner
class  Something():
    @deco
    def foo(self):
        print("foo")

a = Something()
print(a.foo())

image.png

來(lái)實(shí)際抽象一波

我曾經(jīng)封裝過(guò)一些 python 中的 SQL 方法,其中有一些非常無(wú)聊的操作:

import MySQLdb

class MySqlSearch():
    def __init__(self):
        pass

    def get_conn(self):
        self.conn = MySQLdb.connect(
            # ...
        )

    def conn_close(self):
        if self.conn:
            self.conn.close()

    def get_one(self, order='id'):
        self.get_conn()
        # ...
        self.conn_close()
        return result

    def get_all(self, order='id'):
        self.get_conn()
        # ...
        self.conn_close()
        return result

    def get_by_page(self, page=1, page_size=10, order='id'):
        '''根據(jù)頁(yè)面顯示數(shù)據(jù),默認(rèn)第一頁(yè)起算,一頁(yè)有十行數(shù)據(jù)'''
        self.get_conn()
        # ...
        self.conn_close()
        return result


def main():
    obj = MySqlSearch()
    print(obj.get_one())
    print('-'*50)
    print(obj.get_all())
    print('-'*50)
    print(obj.get_by_page())


if __name__ == '__main__':
    main()

啊,十分明顯的,為了不長(zhǎng)時(shí)間占用與數(shù)據(jù)庫(kù)的鏈接,每次我都需要開(kāi)關(guān)數(shù)據(jù)庫(kù)的鏈接,太麻煩了。

很明顯,可以將開(kāi)關(guān)數(shù)據(jù)庫(kù)操作給封裝掉:

import MySQLdb

def mysql_open_close_decorator(func):
    def __foo(self):
        self.conn = MySQLdb.connect(
                    # ...
        )
        result = func(self) # 實(shí)際操作
        if self.conn:
            self.conn.close()
        return result
    return __foo

class MySqlSearch():
    def __init__(self):
        pass

    @mysql_open_close_decorator
    def get_one(self, order='id'):
        # ...
        return result

    @mysql_open_close_decorator
    def get_all(self, order='id'):
        # ...
        return result

    @mysql_open_close_decorator
    def get_by_page(self, page=1, page_size=10, order='id'):
        '''根據(jù)頁(yè)面顯示數(shù)據(jù),默認(rèn)第一頁(yè)起算,一頁(yè)有十行數(shù)據(jù)'''
        # ...
        return result


def main():
    obj = MySqlSearch()
    print(obj.get_one())
    print('-'*50)
    print(obj.get_all())
    print('-'*50)
    print(obj.get_by_page())


if __name__ == '__main__':
    main()

保留被裝飾函數(shù)的元信息

問(wèn)題:假設(shè)你寫(xiě)了裝飾器來(lái)裝飾一個(gè)函數(shù),而我們運(yùn)行時(shí)其實(shí)運(yùn)行的是裝飾器并在其中調(diào)用被裝飾的函數(shù),所以被裝飾函數(shù)不是被直接調(diào)用的,這樣一來(lái)重要的元信息比如函數(shù)名稱(chēng)、文檔字符串、注解和參數(shù)簽名等等信息都會(huì)不會(huì)被保留,此時(shí)我們能看到只有直接調(diào)用的裝飾器的元信息。

def deco(func):
     def __funny():
         '''裝飾器的文檔字符串'''
         print("2")
         func()  # 核心
         print("3333")
         return None  # 注意返回 None
     return __funny

@deco
def foo():
    '''被裝飾函數(shù)的文檔字符串'''
    print("foo")
image.png

裝飾一個(gè)函數(shù),核心在于這個(gè)函數(shù)而不是裝飾器,所以我們更希望我們裝飾過(guò)的函數(shù)能夠保留所有的原始信息,可以自己寫(xiě),但更推薦使用 functools 庫(kù)提供的 @wraps 裝飾器。

from functools import wraps
def deco(func):
     @wraps(func) # @wraps 裝飾器,注意傳入被裝飾函數(shù)來(lái)保留其元信息
     def __funny():
         '''裝飾器的文檔字符串 '''
         print("2")
         func()  # 核心
         print("3333")
         return None  # 注意返回 None
     return __funny

@deco
def foo():
    '''被裝飾函數(shù)的文檔字符串'''
    print("foo")

image.png

@wraps 有一個(gè)重要特點(diǎn)是它能讓你通過(guò)屬性 wrapped 來(lái)直接訪問(wèn)被包裝函數(shù),比如上圖中的 foo.wrapped()。

functools 所提供的 wraps 作用于裝飾器,保留被裝飾函數(shù)的元信息和提供一份裝飾器的代碼副本。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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