Python裝飾器

這段時間因爲工作忙,已經很久沒更新博客了。前幾天看到一個開源項目,使用django框架,後臺用Python,前臺用JavaScript。看後臺源碼時,總是遇到@什麼什麼。因爲以前算是系統的學過Python,所以知道這是Python的裝飾器,但對這一知識點的理解還是自認不夠,看代碼還是比較吃力。今天決定寫這個博客,一來是梳理一下自己頭腦中的這一知識點,二來是做個記錄,便於日後查閱,三來是給跟我一樣對這一知識點理解不透徹的人一點幫助。
Python裝飾器實際上是一個Python函數,參數是一個函數,返回值還是一個函數。先來看裝飾器這三個字,顧名思義,它就是用來裝飾別人的,那麼裝飾誰呢?被裝飾的對象就放在參數中,那麼裝飾後的結果呢?裝飾後的結果就用返回值返回。
來看一個簡單的例子:

def funcA():
    print "in funcA."

def funcB():
    '''This is funcB.'''
    print "in funcB."

funcA()
funcB()

現在我想做這樣一件事情,如果某個函數有函數說明,我們就把這個函數的函數說明打印出來;如果沒有,就打印函數名 + ‘has no doc’這句話。並且提出一個最基本要求,就是函數的定義以及函數的調用方式必須保持不變。這裏爲什麼要提這個要求,因爲如果函數的調用方式改變的話,那麼你就要改變每一處調用該函數的代碼,如果調用該函數的地方多了(一個文件還好,跨文件就比較麻煩了),工作量可想而知。
這個時候裝飾器就能派上用場了。
我們可以這樣改:

def decorator(func):
    def wrap():
        if func.__doc__ == None:
            print func.__name__ + " has no doc."
        else:
            print func.__name__ + "'s doc is " + func.__doc__

        func()
    return wrap

def funcA():
    print "in func."
funcA = decorator(funcA)

def funcB():
    '''This is funcB.'''
    print "in funcB."
funcB = decorator(funcB)   

funcA()
funcB()

上面的decorator函數就是我們定義的裝飾器,參數是func,就是要裝飾的函數,返回值是裝飾後的函數。我們只是定義了一個裝飾器函數,並用該裝飾器裝飾了funcA和funcB,函數funcA和函數funcB的定義和調用方式我們沒做任何改變。這樣就滿足了需求,不過也有一個問題,那就是上面這個裝飾器的侷限性很大,比如我這裏定義一個函數funcC,如下:

def funcC(par1, par2, par3):
    '''This is funcC '''
    print str(par1) + str(par2) + str(par3)

這個時候就不能用decorator來裝飾funcC(不信可以去試一下),爲什麼?最直觀的解釋就是被裝飾的函數有三個參數,而裝飾後的函數無參數,破壞了一致性。這就好像一個人去理髮店,理髮前和理髮後應該還是同一個人,不能說去了一趟理髮店,這個人就不是這個人了。還可以這樣解釋,程序是順序執行的,它會按照代碼一行一行的執行。當你用funcC = decorator(funcC) 來裝飾funcC後,這個時候funcC就不再是funcC了,而是函數wrap,而函數wrap是無參數的,所以當你調用funcC(1, 2, 3)的時候會報錯。錯誤信息類似下面這樣:
這裏寫圖片描述
那麼我想讓decorator既可以裝飾funcA和funcB,又可以裝飾funcC,怎麼辦呢?剛纔出錯的原因就是裝飾後的函數的參數與裝飾前的函數的參數匹配不上,看到這,有經驗的程序員裏面想到了解決方案:*args, **kwargs。沒錯,就是它了,它可以匹配任意參數。不管裝飾前你的參數是什麼樣子,不管你有幾個參數,1個?2個?3個?(i don’t care.)我都用這個來匹配。代碼如下:

def decorator(func):
    def wrap(*args, **kwargs):
        if func.__doc__ == None:
            print func.__name__ + " has no doc."
        else:
            print func.__name__ + "'s doc is " + func.__doc__

        func(*args, **kwargs)
    return wrap

def funcA():
    print "in func."
funcA = decorator(funcA)

def funcB():
    '''This is funcB.'''
    print "in funcB."
funcB = decorator(funcB)  

def funcC(par1, par2, par3):
    '''This is funcC '''
    print str(par1) + str(par2) + str(par3)
funcC = decorator(funcC) 

funcA()
funcB()
funcC(1, 2, 3)

注意在wrap函數內,函數調用也改成了func(*args, **kwargs)。
在上面我們可以發現一個共同點,如:

def func(...):
    ...
func = decorator(func)

每次這樣寫顯得比較麻煩,我們可以簡寫成下面這樣:

@decorator
def func(...):
    ...

其實上面兩種寫法的效果完全一樣。只是下面那種寫法顯得簡潔,可以顯得你更牛X。不要以爲@有什麼好神祕的,其實在生活中我們不是經常用@嗎?我們呼叫某個人時,或者評論某個人的說說時,就會用到@啊!@decorator,你可以把它理解爲,“喂,decorator,你幫我裝飾一下下面定義的這個函數”。好了,知道了這個,我們可以把代碼修改成下面這樣:

def decorator(func):
    def wrap(*args, **kwargs):
        if func.__doc__ == None:
            print func.__name__ + " has no doc."
        else:
            print func.__name__ + "'s doc is " + func.__doc__

        func(*args, **kwargs)
    return wrap

@decorator
def funcA():
    print "in func."

@decorator    
def funcB():
    '''This is funcB.'''
    print "in funcB."  

@decorator
def funcC(par1, par2, par3):
    '''This is funcC '''
    print str(par1) + str(par2) + str(par3)

funcA()
funcB()
funcC(1, 2, 3)

再來個更復雜一點的,我們可以對裝飾器decorator再來一層封裝,如下面這樣:

def decorator_outer(par):
    def decorator(func):
        def wrap(*args, **kwargs):
            if func.__doc__ == None:
                print func.__name__ + " has no doc."
            else:
                print func.__name__ + "'s doc is " + func.__doc__
            print par
            func(*args, **kwargs)
        return wrap
    return decorator

我們在最外層定義了一個函數decorator_outer,它有一個參數par,這個函數返回我們以前的那個裝飾器函數。這個decorator_outer就是新的裝飾器了。
然後下面也要改成這樣:

@decorator_outer("haha")
def funcA():

完整的代碼如下:

def decorator_outer(par):
    def decorator(func):
        def wrap(*args, **kwargs):
            if func.__doc__ == None:
                print func.__name__ + " has no doc."
            else:
                print func.__name__ + "'s doc is " + func.__doc__

            print par

            func(*args, **kwargs)
        return wrap
    return decorator

@decorator_outer("haha")
def funcA():
    print "in func."

@decorator_outer("hehe")   
def funcB():
    '''This is funcB.'''
    print "in funcB."  

@decorator_outer("hoho")
def funcC(par1, par2, par3):
    '''This is funcC '''
    print str(par1) + str(par2) + str(par3)

funcA()
funcB()
funcC(1, 2, 3)

這樣的裝飾器,我們把它稱爲“帶參數的裝飾器”。這個也好理解,它其實就是對舊的裝飾器的一種封裝而已。
原文地址:http://tanlian.co/2015/11/26/Python%E8%A3%85%E9%A5%B0%E5%99%A8/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章