06.13 Python 裝飾器 Decorators

Python 裝飾器 Decorators

明白 Python 中的裝飾器是Python程序員的一個里程碑。通過這篇文章,你會學習到怎麼使用裝飾器去寫出更有效的代碼。

使用裝飾器,使你能夠擴展函數、方法和類的默認行為,而不需要修改原來的代碼。這些行為可能有:

  • 記錄日誌
  • 添加訪問控制授權
  • 計算程序運行時間
  • 控制調用的頻率
  • 緩存

為什麼要使用裝飾器


為什麼要使用裝飾器呢,這詞兒聽起來好像很難的樣子。除了能給你帶來別人頂禮膜拜的表情外,它使你在往 Python 高級程序員的路上越走越近。

假如你的程序有60個方法,老闆想讓你在一些方法中添加日誌或者統計一些信息。怎麼辦,如果不使用裝飾器,你必然要在每個方法裡添加代碼。

而使用裝飾器,只需要在這些方法中的定義上方加入裝飾器。 語法格式為:

Python 裝飾器 Decorators

在繼續深入之前,你需要了解的是Python的函數是第一類對象(first-class object),函數也是一個對象。

  • 函數可以賦值給一個變量,也可以當參數一樣傳入另一個函數,函數可以返回一個函數對象。
  • 函數里可以定義另一個函數,叫內部函數或者嵌套函數,內部的函數可以訪問外面的函數的變量。

裝飾器基礎


現在看一下什麼是裝飾器。

裝飾器就是包裝了一個函數,讓你在包裝的函數運行之前和之後,執行一些操作。

裝飾器讓你封裝一些可重用的操作,修改函數的默認行為,這樣可以不修改之前函數內部的代碼。

現在看一下怎麼實現一個簡單的裝飾器,裝飾器是可調用的(callable),它接受一個參數是可調用的對象,返回一個可調用的對象。

下面的函數符合裝飾器的特性:

Python 裝飾器 Decorators

可以看到,do_nothing() 是可調用的(callable),它是一個函數,並且接收一個可調用的參數,然後返回。

讓我們給它包裝另一個函數:

Python 裝飾器 Decorators

在上面的例子中,生成了一個可調用的函數 hi(),它是用 do_nothing()函數包裝的另一個函數 hello()。實際執行了 hello() 的代碼,輸出 hello。

Python提供了 @syntax , 不需要 do_nothing() 函數調用並賦值一個新變量。如下:

Python 裝飾器 Decorators

把 @do_nothing 直接放在 hello() 函數的定義上方。

注意 @syntax 語法直接在定義階段就更改了函數的默認行為,使你很難再訪問原來的方法了。因此使用的時候要做好判斷,怎麼保留和執行之前的函數代碼。

裝飾器修改函數的默認行為


現在已經瞭解了裝飾器的用法了,那面裝飾器是否可以修改原來函數的默認行為呢。看下面的例子。

Python 裝飾器 Decorators

輸出:

HELLO

看到結果字符串 HELLO,原來的輸出小寫變成了大寫,成功修改了之前函數的輸出。

看一下 uppercase() 裝飾器,它首先是符合裝飾器的特性的,輸入一個可調用的函數,返回的是一個閉包函數 newfunc() ,因為 newfunc() 函數位於 uppercase() 函數的裡面,所以他可以訪問參數 func 。

newfunc() 調用了原來的函數,然後把結果轉換為大寫。

這個例子體現了,裝飾器可以不用修改函數內部的代碼,也可以把默認的行為修改。這是很有用的特性,在Python的標準庫和第三方庫中大量的使用了裝飾器。

一個函數上使用多個裝飾器


你可以在一個函數上使用多個裝飾器。請看下面的例子:

Python 裝飾器 Decorators

以上輸出:

hello

這個例子在字符串外加了兩個HTML標籤。可以看到,多個裝飾器的應用順序是從下到上,首先應用 em() 裝飾器,而後是 strong() 裝飾器,輸出結果看 在最外層。

相當於:newfunc = strong(em(hello()))

裝飾器函數接收參數


上面的例子,都沒有傳遞參數,那參數怎麼從裝飾器傳入到被裝飾的函數上呢?可以使用 Python 的*args 和 *kwargs 特性。下面的語法描述了這種情況:

Python 裝飾器 Decorators

使用 *args 和 **kwargs 將裝飾器接收到的參數都傳入到實際的函數中。

看下面一個簡單的例子:

Python 裝飾器 Decorators

輸出:

Python 裝飾器 Decorators

實際的函數 hello(name, age) 成功接收到了參數。

裝飾器使用 functools.wraps


當一個函數使用了裝飾器後,函數本來的名稱,文檔字符串(docstring)都被裝飾器隱藏了。請看下面例子:

Python 裝飾器 Decorators

輸出為:

newfunc 

proxy newfunc

hello() 函數加了裝飾器後,__name__ 屬性變成了 newfunc,__doc__ 文檔字符串為 proxy newfunc。它返回的是裝飾器內部函數 newfunc 的信息

這樣調試程序會比較麻煩,幸好Python標準庫為我們提供了裝飾器函數 functools.wraps 。你可以在裝飾器內部函數上使用它,把被裝飾的函數的信息複製到內部函數上。

Python 裝飾器 Decorators

輸出:

hello
hello someone

成功輸出了原始函數的信息。

每次使用裝飾器,最好都使用 functools.wraps,這樣調試就不會花太多時間。

裝飾器總結


  • 裝飾器定義了一個可重用的代碼塊,你可以應用到一個可調用的對象,在調用之前或者之後,修改對象的行為。
  • @syntax 在一個可調用的對象上應用一個裝飾器,可以添加多個裝飾器,裝飾的順序是由下到上。
  • 為了調試方便,你應該總是使用 functools.wraps 將原始的信息複製到裝飾器返回的內部對象上。


分享到:


相關文章: