Python的@裝飾器的作用小結

我們在編程過程中,常常會遇到這種需求:

比如,我想開發一款計算器,我已經寫好瞭一堆函數,用於執行各種計算,那麼我們需要在執行各種計算函數前,首先對輸入的數據進行檢查,確保他們必須得是數值才允許執行函數,而不能是字符串;

又如,我想編寫一個用於計算三角形周長、面積、某個角角度的模塊,已經寫好幾個函數用於計算,那麼,在執行計算前,首先要確保輸入的三條邊長能夠構成三角形,再進行計算才有意義;

再比如,我想開發某款網絡應用,寫瞭一些函數用於實現用戶的某些操作,那麼,得要先檢查確認該用戶已經登錄瞭,才允許執行這些操作

這些需求,歸納起來,就是,在執行主函數之前,常常要先執行某個預函數,進行一些校驗之類的操作。

這類需求是非常常見的,也是保證程序完整性、健壯性的重要舉措。所以,怎麼做才比較簡單呢?

你會說,這很簡單啊,在每個函數裡面寫上if語句不就得瞭。就拿那個計算器而言,如果我們要寫加減乘除,我們可以這樣:

def plus(a,b):
    if type(a)==type(0) and type(b)==type(0): #假設該計算器隻能計算整數,如果要計算小數再or type(0.0)
        return a+b
    else:
        print('Type must be number') #檢測到數據類型不對,先輸出報警,函數值返回None
        return None

def minus(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a-b
    else:
        print('Type must be number')
        return None

def multiply(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a*b
    else:
        print('Type must be number')
        return None

def divide(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a/b
    else:
        print('Type must be number')
        return None

這個嘛,直接暴力。但是呢,這裡隻有4個函數,假如你開發的計算器有幾十幾百個函數,每個函數都要套上if語句,這不得麻煩死瞭,不煩死也囉嗦死瞭

所以怎麼弄簡單一點呢?聰明的你肯定想到瞭,我們可以把那個判斷if也單獨定義一個函數,然後把計算用的函數套在裡面,就像這樣:

def check(a,b,func): #定義檢查函數,變量為待檢測參數a,b和檢測通過後執行的函數func
    if type(a)==type(0) and type(b)==type(0):
        return func(a,b)
    else:
        print('Type must be number')
        return None

def plus(a,b):
    return a+b

def minus(a,b):
    return a-b
...

#主程序
check(1,2,plus) #計算1+2
check(1,2,minus) #計算1-2
check(1,2,multiply) #計算1*2
check(1,2,divide) #計算1/2

這裡面有一點一定要特別註意,主程序的check(1,2,plus) 是把plus函數本身作為變量傳遞給check,由check函數決定如何執行plus函數,此處不能寫成check(1,2,plus(1,2)),plus不能帶參數和括號,不是執行plus()後把結果傳給check。

這麼寫程序簡潔瞭不少,加減乘除函數隻需要定義他們本身的運算就可以瞭,變量檢測交給瞭check函數。這麼寫也是比較容易理解的。

但是對於使用該程序的用戶來說,就不是這麼回事瞭,他們會覺得這麼寫非常難看

為什麼呢?我是要拿程序做加減乘除計算的,但我不論計算什麼,每次都是在主調用check這個函數

那有沒有什麼辦法,可以既好看,又簡潔呢?裝飾器就是起到瞭這個神奇的作用。

上面這個需求,用裝飾器可以這麼寫:

def check(func):
    ...

@check
def plus(a,b):
    return a+b

@check
def minus(a,b):
    return a-b

...

#主程序
plus(1,2) #計算1+2
minus(1,2) #計算1-2
...

先直觀感受一下,通過@check,check函數就被“註入”到瞭plus函數中,使得plus函數擁有瞭參數檢測的功能。這樣,在主程序中,若要計算加法就可直接調用plus,便可先校驗再計算。

那麼,這個裝飾器check要怎麼定義呢?我們來看一下。

def check(func): #定義裝飾器check
    def newfunc(a,b): #定義函數模板,即如何處理func
        if type(a)==type(0) and type(b)==type(0):
            return func(a,b)
        else:
            print('Type must be number!')
            return None
    return newfunc #將處理後的func作為新函數newfunc輸出

@check
def plus(a,b):
    return a+b

#主程序,計算1+2
plus(1,2)

我們可以看到,當裝飾器@check作用於plus函數時,plus函數本身作為參數func傳入裝飾器中。在裝飾器check的定義內部,定義瞭一個函數模板,描述瞭對輸入的func如何處理。可以看到,newfunc對func(也就是輸入的plus)套用瞭判斷數據類型的if語句,最後,再將套好的newfunc輸出,替代原來的func。這樣,此時執行func就是在執行newfunc,執行plus就是在執行套上if語句的新函數。

所以,通過裝飾器,添加上瞭判斷語句的新函數替換瞭原來的plus函數,但仍通過plus這個函數名調用,所以看起來就是plus函數被“裝飾”瞭。

當然瞭,如果大傢在網絡上搜索,關於如何定義裝飾器,看到的是一個更加規范的版本。看起來更難理解一些,但其實是一樣的:

def checkall(func):
    def wrapper(*args,**kwargs):
        if type(args[0])==type(0) and type(args[1])==type(0):
            return func(*args,**kwargs)
        else:
            print('Type must be number!')
            return None
    return wrapper

模板函數一般習慣用wrapper來表示,這個沒啥,建議大傢都這麼寫,規范一些。

參數一般用不定長的*args,**kwargs來表示,這個可能有些人就困惑瞭。因為被裝飾的函數可能有很多種,參數的個數一般也不確定。然後*args,**kwargs是什麼東西?args,kwargs這兩個形參英文字母是什麼無所謂可以自己定,關鍵是前面的單星號*和雙星號**。

假如我定義一個函數,不能確定參數有多少個,例如要對輸入的一組數字做連加操作。那麼就可以定義plus(*x),當調用該函數時,若輸入多個變量plus(1,2,3),那麼就會把輸入的變量組合成一個元祖x=(1,2,3)輸入。定義雙星號plus(**x)的意思是,調用該函數時若寫出形參變量plus(a=1,b=2,c=3),那麼輸入變量就會組合成字典x={a:1,b:2,c:3}傳入函數。

當然也可以反向操作,定義函數的時候參數個數是明確的plus(a,b,c),那麼調用該函數時,加上星號plus(*(1,2,3)),就是對輸入元祖(1,2,3)執行炸開操作,轉換為plus(1,2,3)輸入。

裝飾器裡這麼寫有什麼用呢?我們仔細觀察一下我們之前寫的newfunc(a,b),那就意味著,指明瞭新函數有兩個參數a,b,假如被裝飾的原函數有三個參數怎麼辦呢?不就沒用瞭嗎?

我們來看別人寫的,定義時用瞭wrapper(*args,**kwargs),即不管有多少個參數,打包輸入wrapper。在wrapper當中,調用原函數時又func(*args,**kwargs),即把輸入的元祖解包再傳入func。這麼一打包一解包,雖然看起來啥都沒幹,但確適應瞭函數參數不確定的情況,使得該裝飾器可以裝飾多種參數數量不同的函數

就先這樣吧。

到此這篇關於Python的@裝飾器是幹什麼用的?的文章就介紹到這瞭,更多相關Python @裝飾器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: