python之裝飾器

裝飾器(Decorator)

  使用場景:爲被裝飾器裝飾的函數增加功能,但又不希望修改函數的定義,即在代碼運行期間動態增加功能。

  裝飾器更多的用於後期功能升級而不是編寫新的代碼。裝飾器不光能裝飾函數,也能裝飾其他的對象,比如類,但通常,我們以裝飾函數爲例子介紹其用法。


理解裝飾器需要具備的知識:

    函數即“變量”、高級函數、嵌套函數


例如爲已經運行的代碼中的多個函數增加同一個功能,方法有:

  1、對基礎平臺的代碼進行重構,即在每段函數中都加入相應代碼。

  2、把需要的功能寫成一個新函數,在需要加功能的函數中調用它。

  3、使用裝飾器,對基礎平臺的代碼進行拓展。

注:開放封閉原則:

   已經實現的功能代碼內部不允許被修改,但外部可以被擴展。主要是針對面向對象開發,但是也適用於函數式編程。

   封閉:已實現的功能代碼塊;

   開放:對擴展開放。

所以使用裝飾器這種方式爲最佳。


原理

例如要在調用函數中添加認證和日誌記錄功能(

In [23]: def outer(func):

    ...:     def inner():

    ...:         print('認證成功!')

    ...:         result = func()

    ...:         print('日誌添加成功')

    ...:         return result

    ...:     return inner

    ...: 

 

In [24]: @outer        # f1 = outer(f1) = inner

    ...: def f1():

    ...:     print('業務部門1數據接口...')

    ...:     


In [25]: f1()

認證成功!

業務部門1數據接口...

日誌添加成功

  1、程序開始運行,從上往下編譯,讀到def outer(func):的時候,發現這是個“一等公民”->函數,於是把函數體加載到內存裏,然後過。

  2、讀到@outer的時候,程序被@這個語法糖吸引住了,知道這是個裝飾器,按規矩要立即執行的,於是程序開始運行@後面那個名字outer所定義的函數。(裝飾器只能放在被裝飾的函數的上方最近處,不要空行。)

  3、程序返回到outer函數,開始執行裝飾器的語法規則,這部分規則是定死的,是python的“法律”。

規則是:被裝飾的函數的名字會被當作參數傳遞給裝飾函數。裝飾函數執行它自己內部的代碼後,會將它的返回值賦值給被裝飾的函數。 

  4、程序開始執行outer函數內部的內容,一開始它又碰到了一個函數。inner函數定義塊被程序觀察到後不會立刻執行,而是讀入內存中(這是潛規則)。

  5、再往下,碰到return inner,返回值是個函數名,並且這個函數名會被賦值給f1這個被裝飾的函數,也就是f1 = inner。根據前面的知識,我們知道,此時f1函數被新的函數inner覆蓋了(實際上是f1這個函數名更改成指向inner這個函數名指向的函數體內存地址,f1不再指向它原來的函數體的內存地址),再往後調用f1的時候將執行inner函數內的代碼,而不是先前的函數體。那麼先前的函數體去哪了?還記得我們將f1當做參數傳遞給func這個形參麼?func這個變量保存了老的函數在內存中的地址,通過它就可以執行 老的函數體,你能在inner函數裏看到result = func()這句代碼,它就是這麼幹的!

  6、接下來,還沒有結束。當業務部門,依然通過f1()的方式調用f1函數時,執行的就不再是老的f1函數的代碼,而是inner函數的代碼。在本例中,它首先會打印個“認證成功”的提示,很顯然你可以換成任意的代碼,這只是個示例;然後,它會執行func函數並將返回值賦值個變量result,這個func函數就是老的f1函數;接着,它又打印了“日誌保存”的提示,這也只是個示例,可以換成任何你想要的;最後返回result這個變量。我們在業務部門的代碼上可以用 r = f1()的方式接受result的值。

  7、以上流程走完後,你應該看出來了,在沒有對業務部門的代碼和接口調用方式做任何修改的同時,也沒有對原有的代碼做內部修改,僅僅是添加了一個裝飾函數,就實現了我們的需求,在函數調用前先認證,調用後寫入日誌。這就是裝飾器的最大作用。


但爲什麼我們要搞一個outer函數一個inner函數這麼複雜呢?

  注意,@outer這句代碼在程序執行到這裏的時候就會自動執行outer函數內部的代碼,如果不封裝一下,在還未進行調用的時候,就執行了些什麼,這和初衷有點不符。當然,如果你對這個有需求也不是不行。請看下面的例子,它只有一層函數。  

In [33]: def outer(func):

    ...:     print('認證成功!')

    ...:     result = func()

    ...:     print('日誌添加成功')

    ...:     return result

    ...: 

 

In [34]: @outer

    ...: def f1():

    ...:     print('業務部門1數據接口...')

    ...:     

認證成功!

業務部門1數據接口...

日誌添加成功

 

上面並沒有執行f1函數,只是定義好了,還沒有調用f1函數,程序就把工作全做了。這就是封裝一層函數的原因。


原則

  1、不能修改被裝飾的函數的源代碼

  2、不能修改被裝飾的函數的調用方式



參數傳遞

被裝飾函數的參數傳遞

  例如部門調用的接口函數都需要不同的參數,需要使用 *args **kwargs 。

In [17]: def outer(func):

    ...:     def inner(*args, **kwargs):

    ...:         print('認證成功')

    ...:         result = func(*args, **kwargs)

    ...:         print('日誌添加成功')

    ...:         return result

    ...:     return inner

    ...: 

 

In [18]: @outer

    ...: def f1(name, age):

    ...:     print("{}正在連接業務部門1數據接口。。。".format(name))

    ...:     

 

In [19]: f1("jack", 18)

認證成功

jack正在連接業務部門1數據接口。。。

日誌添加成功


裝飾器自己的參數

  裝飾器自身的參數不能直接傳入所以在裝飾器,在裝飾器外再封裝一層函數的目地就是爲了給裝飾器傳參。

In [31]: # 認證函數

In [32]: def auth(request, kwargs):

    ...:     print("認證成功!")

    ...:     

 

In [33]: # 日誌函數

In [34]: def log(request, kwargs):

    ...:     print("日誌添加成功!")

    ...:     

 

In [35]: # 裝飾器函數,接收2個參數,這兩個參數應該是某個函數的名字。

In [41]: def Filter(auth_func, log_func):

    ...:     # 第一層封裝,auth和log函數的參數值被傳遞到了這裏

    ...:     def outer(main_func):

    ...:         # 第二層封裝,f1函數實際上被傳遞給了main_func這個參數

    ...:         def wrapper(request, kargs): 

    ...:         # 下面代碼的判斷邏輯不重要,重要的是參數的引用和返回值

    ...:             before_result = auth(request, kargs) 

    ...:             if(before_result != None):

    ...:                 return before_result

    ...:             main_result = main_func(request, kargs)

    ...:             if(main_result != None):

    ...:                 return main_result

    ...:             after_result = log(request, kargs)

    ...:             if(after_result != None):

    ...:                 return after_result

    ...:         return wrapper

    ...:     return outer

    ...: 

    # 注意了,這裏的裝飾器函數有參數哦,它的意思是先執行Filter函數

    # 然後將Filter函數的返回值作爲裝飾器函數的名字返回到這裏,所以,

    # 其實這裏等價於,Filter(auth,log) = outer , @Filter(auth,log) =  @outer

     #  也等價於f1=Filter(auth, log)(f1)=@outer=wrapper

     

In [44]: @Filter(auth, log)  

    ...: def f1(name, age):

    ...:     print("{} 正在連接業務部門1數據接口。。。".format(name))

    ...:     

 

In [45]: f1("jack", 18)

認證成功!

jack 正在連接業務部門1數據接口。。。

日誌添加成功!

可以這麼理解,先執行Filter函數,獲得它的返回值outer,再執行@outer裝飾器語法。


一個函數可以被多個函數裝飾。


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