python黑魔法---裝飾器(decorator)

發表於2017-02-03

python 是一門優雅的語言,有些使用方法就像魔法一樣。裝飾器(decorator)就是一種化腐朽性為神奇的技巧。最近一直都在使用 Tornado 框架,一直還是念念不忘 Flask 。Flask 是我最喜歡的 Python 框架,最早被它吸引也是源自它使用裝飾器這個語法糖(Syntactic sugar)來做 Router,讓程式碼看上去就感覺甜甜的。

Tornado 中的 Router 略顯平淡,懷念 Flask 的味道,於是很好奇的想知道 Flask 是如何使用這個魔法。通過閱讀 Flask 的原始碼,我們也可以為 Tornado 實現了一個裝飾器 Router。

當然對於剛接觸 Python 的人,也許很容易理解裝飾器本質是設計模式中的裝飾器模式。可是 Python 通過@一個實現裝飾器的語法糖。本文的目的就是讓 @ 不再神祕。

一切都是物件

Python 裡一切都是物件,當然這不代表一切都是女朋友。函式也是物件,因而可以當成引數傳遞,例如:

我們的 greet 函式的引數,也是一個函式物件。可以傳遞這個引數物件。我們呼叫greet的時候,greet 內部進行函式引數的呼叫。

裝飾模式

裝飾模式,顧名思義,就是在呼叫目標函式之前,對這個函式物件進行裝飾。比如一個對資料庫操作的方法,我們在查詢資料之前,需要連線一下資料庫,當查詢結束之後,需要再把連線斷開關閉。正常的邏輯如下:

我們把 連線資料庫(connect_db) 和 關閉連線 (close_db)都封裝成了函式。 query_data 方法執行我們查詢的具體邏輯。這樣需要不同的查詢方法,只需要把查詢的邏輯也封裝成一個方法就Okla

把查詢的函式物件傳進來,符合開篇說的一切都是物件。裝飾器完成啦。對,就這麼簡單,query_data 就是對 query_user 的裝飾,當然你還可以寫出 query_blog 等方法。

等等,設想一種情況,在我們使用裝飾函式之前,專案的程式碼已經有了大量的 query_user方法的呼叫。如果使用了query_data 包裝。我們就不得不把之前 query_user() 的地方統統替換成 query_data(query_user)。怎麼樣才能減少對程式碼的改動呢?

我們的出發點是為了保持之前的 query_user() 不改動,現在實際情況是呼叫 query_data(query_user)。如果 query_data 呼叫的時候,返回一個函式呢?例如下面的程式碼:

這樣一個完整的裝飾器就完成了,比起前面的版本,我們不需要改動之前寫好的 query_user 程式碼。一個關鍵點在於query_data 呼叫的時候,返回了一個 wrapper 函式,而這個wrapper 函式執行 query 函式呼叫前後的一些邏輯。另外一個關鍵就是呼叫裝飾器 query_data 裝飾函式。

語法糖@

前面的程式碼,可以使用 python的裝飾器語法糖@,如下:

前面的 裝飾器 呼叫進行裝飾的時候,python 有一個語法糖。
如果給裝飾器函式前面加一個@,我們可以理解為呼叫了一些裝飾器函式,即 @query_data 等於 query_data()。當實際上,並不是這麼使用,而是這麼一個整體:

等價於

被裝飾函式引數

我們被裝飾的函式,往往帶有引數,因此通過裝飾器如何傳遞引數呢?回想一下,裝飾器函式針對被裝飾的函式進行裝飾,使用的是返回一個 wrapper 函式。其實這個函式可以等同於被裝飾的函式,只不過 wrapper 還做了更多的事情。被裝飾的函式引數可以通過 wrapper 傳遞。如下:

這樣就實現了被裝飾的函式傳遞引數。當然,位置引數和關鍵字引數,可變引數都可以。

裝飾器引數

在 flask 中,對檢視函式的裝飾是裝飾器中傳遞 url 正則,即在裝飾器中傳遞引數,和被裝飾器的引數還不一樣。

我們如何定義router這個裝飾器呢?其實只要在原先的裝飾器外面再包裹一層,也就是針對裝飾器進行裝飾。

@router() 這個語法糖看上去讓人迷惑,其實也很好理解。這裡可以看成兩個步驟
第一步是呼叫 router 這個函式:

第二步則進行裝飾:

連起來的效果就是

現在回想,@ 這個語法糖很甜吧。並且和python一樣很好理解,也十分常用。

當然,我們使用 裝飾器是為了實現一些需要包裝的方法,例如前面提到的 flask 的 router

有人已經寫了一篇很棒的 Tutorial: Things which aren’t magic – Flask and @app.route,可以參考加深對裝飾確定理解,裝飾器還有很多用途.

Enjoy~

相關文章