1. 程式人生 > >python快速學習系列(5):裝飾器

python快速學習系列(5):裝飾器

裝飾器概述:
-理解裝飾器要從三方面入手:why?what?how?
-學習裝飾器要從模仿開始

1.why?為什麼會出現裝飾器這個東西?
·名稱管理
·顯示呼叫
·就近原則
·充分複用
例如:

def decorate(func):
    func.__doc__ += '\nDecorated by decorate'
    return func

def add(x,y)
    '''Return the sum of x and y.'''
    return x + y

add = decorate(add)

上述寫法是沒有使用裝飾器的寫法,改進如下:

def decorate(func):
    func.__doc__ += '\nDecorated by decorate'
    return func

@decorate       #@是一個新的語法。這句話替代了add = decorate(add)
def add(x,y)
    '''Return the sum of x and y.'''
    return x + y

而上述的寫法對於小程式已經足夠了,但是對於大型程式來說,複用是非常重要的,
可以將所有的裝飾器封裝到一個類中,然後再呼叫,例如:
DecorateToolBox.py

class DecorateToolBox:
    def decorate(self,func):
        func.__doc__ += '\nDecorated by decorate'

test.py

from DecorateToolBox import DecorateToolBox
@DecorateToolBox.decorate
def add(x,y):
    '''Return the sum of x and y'''
    return x + y

2.what?什麼是裝飾器?
·裝飾器是一個可呼叫的物件,以某種方式增強函式的功能(注意是增強而不是修改)
·裝飾器是一個語法糖,在原始碼中標記函式(此原始碼指編譯後的原始碼)
·直譯器解析原始碼的時候將被裝飾的函式作為第一個位置引數傳給裝飾器
(直譯器在解析原始碼的時候,只要發現了@這個語法糖,就會把下面函式的地址作為第一個引數傳給裝飾器)
·裝飾器可能會直接處理被裝飾函式,然後返回它(一般僅修改屬性,不修改程式碼


·裝飾器也可能用一個新的函式或可呼叫物件替換被裝飾函式(但核心功能一般不變)
·裝飾器僅僅看著像閉包,其實功能的定位與閉包有重合也有很大區別
·裝飾器模式的本質是超程式設計:在執行時改變程式行為
·裝飾器的一個不可忽視的特性:在模組載入時立即執行(很特殊)
·裝飾器是可以堆疊的,自底向上逐個裝飾
·裝飾器是可以帶引數的,但此時至少要寫兩個裝飾器(可帶引數的裝飾器一定是閉包)
·裝飾器的更加pythonic的實現方式其實是在類中實現__call__()方法

1)裝飾器的堆疊

def deco_1(func):
    print('1')
    return func
def deco_2(func):
    print('2')
    return func
@deco_1
@deco_2
def f():
    print('running')
if __name__ == '__main__':
    f()

#結果如下:自下而上
#2
#1
#running

2)裝飾器在匯入時立即執行:將上述程式碼照抄,區別在:main裡面只是pass

def deco_1(func):
    print('1')
    return func
def deco_2(func):
    print('2')
    return func
@deco_1
@deco_2
def f():
    print('running')
if __name__ == '__main__':
    pass

#執行結果如下:
#2
#1
上述結果說明裝飾器在匯入時立即執行

3)帶引數的裝飾器:
既然裝飾器只能接受一個位置引數,並且是被動的接受直譯器傳過來的函式引用,那麼如何實現帶引數的裝飾器呢?
問題分析:
·限制條件一:裝飾器本身只能接受一個位置引數
·限制條件二:這個位置引數已經被裝飾函式的引用佔據了
·問題目標:希望裝飾器能夠使用外部傳入的其他引數
·推論:裝飾器需要訪問或修改外部引數
三種備選方案:
a)在裝飾器內訪問全域性不可變物件,若需要修改,則使用global宣告(不安全)
b)在裝飾器內訪問外部可變物件(不安全)
c)讓裝飾器成為閉包的返回(較安全)
方案:編寫一個閉包,接受外部引數,返回一個裝飾器

registry = set()  #登錄檔
def register(flage=True):
    def decorate(func):
        if flag:
            registry.add(func)
        else:
            registry.discard(func)
        return func   
        #decorate是一個裝飾器,因為它**輸入一個函式,返回一個函式**,而且在中間程式碼裡沒有對原函式進行邏輯上的更改
    return decorate

@register()   #一個裝飾器後面帶不帶(),直接決定了它僅僅是一個裝飾器還是一個閉包
def f1():
    print('running f1')

@register(False)
def f2():
    print('running f2')

@register(True)
def f3():
    print('running f3')

def main():
    f1()
    f2()
    f3()

if __name__ == '__main__':
    print(registry)
    main()

結果如下:
{<function f1 at 記憶體地址>,<function f3 at 記憶體地址>}
running f1
running f2
running f3
分析:
*此時,register變數被使用了兩次
*第一次是後面的呼叫:() (呼叫之後才變成一個裝飾器,注意要把()看成呼叫)
*第二次是前面的裝飾:@ (裝飾器符合僅能用於裝飾器)
注意:register不是裝飾器,register()或register(False)才是

3.How?裝飾器怎麼用?
裝飾器的常見使用場景
·執行前處理:如確認使用者授權
·執行時註冊:如註冊訊號系統
·執行後清理:如序列化返回值

1)註冊機制or授權機制(往往和應用開發相關)
*函式的註冊,參考上面的例子
*將某個功能註冊到某個地方,比如Flask框架中URL的註冊
*比如驗證身份資訊或加密資訊,以確定是否繼續後續的運算或操作
*比如查詢一個身份資訊是否已經被註冊過,如果沒有則註冊,如果有則直接返回賬戶資訊

2)引數的資料驗證或清洗(往往跟資料清洗或異常處理相關)
等等

總結:
·裝飾器作為python四大神器之一,功能非常強大(還有迭代器,生成器,上下文管理器)
·裝飾器用好了能讓程式複用性大大提高,程式碼也會變得非常優雅
·要理解裝飾器,需要先理解一等函式,變數作用域,函數語言程式設計,閉包
·要用好裝飾器,要從模仿開始,多讀別人的程式碼,多模仿,慢慢就有感覺了
·python內建了很多的裝飾器,功能都很強大,辯詞額框架或大型程式的時候一定會用到