Flask 中的裝飾器
總體來說,Python 的語法比較簡單,是一門易學的編程語言。但是,Python 的裝飾器語法是一個例外,裝飾器是 Python 語言的高級語法,牽涉到高階函數等內容,對初學者來說較難理解。
看到這里你可能會問:咱們這門課程不是學習 Flask 框架嗎?怎么又扯到裝飾器上面去了?
這是因為裝飾器語法在 Flask 框架中得到了廣泛的應用,想要學好 Flask 框架不理解裝飾器的概念是不行滴。
因此這個小節我們就專門來討論下裝飾器的用途、定義和原理,為大家深入理解 Flask 框架原理打下一個重要基礎。
1. 高階函數
1.1 把函數當作對象
本節從底層原理講解了如何實現裝飾器,學員需要深入理解 Python 中 “函數是第一類對象” 的概念,可以參考詞條 “Python 的 lambda 表達式”。在這個詞條中對這一概念具有比較系統的講解。
在 Python 中,函數是第一類對象的意思是指函數作為一個對象,與其它對象具有相同的地位。具體來說,一個數值對象可以:
- 被賦值給變量;
- 作為參數傳遞給函數;
- 作為返回值。
因為函數和數值具有相同的地位,所以函數也可以:
- 被賦值給變量;
- 作為參數傳遞給函數;
- 作為返回值。
1.2 把函數作為輸入參數
上面說到函數也可以被作為參數傳遞給另外一個函數,下面我們就用一個例子來演示一下:
def double(item):
return item + item
def triple(item):
return item + item + item
定義函數 double,返回輸入值的 2 倍;定義函數 triple,返回輸入值的 3 倍。
def map(func, input):
output = []
for item in input:
new_item = func(item)
output.append(new_item)
return output
定義函數 map,接受兩個參數:func 和 input。參數 func 是一個函數,參數 input 是一個列表, 對輸入列表 input 中的每個元素依次進行處理,返回一個新列表 output。
在第 3 行,遍歷輸入列表 input 中的每個元素,調用 func (item) 生成一個新的元素 new_item,將 new_item 加入到 output 中,最后返回 output。
print(map(double, [1, 2, 3]))
print(map(triple, [1, 2, 3]))
對序列 [1, 2, 3] 中的每個元素使用函數 double 進行處理;對序列 [1, 2, 3] 中的每個元素使用函數 triple 進行處理。
運行程序,輸出如下:
[2, 4, 6]
[3, 6, 9]
序列 [1, 2, 3] 中的每個元素乘以 2 后,得到序列 [2, 4, 6];序列 [1, 2, 3] 中的每個元素乘以 3 后,得到序列 [3, 6, 9]。
1.3 把函數作為返回值
在下面的例子中,將函數作為返回值:
def func():
print('Inside func')
def return_func():
print('Inside return_func')
return func
在第 1 行,定義函數 func;在第 3 行,定義函數 return_func,函數 return_func 返回一個函數類型的對象,將函數 func 作為值返回。
var = return_func()
var()
調用 return_func (),將函數的返回值保存到變量 var。變量 var 的類型是函數,因此可以進行函數調用。
程序的輸出結果如下:
Inside return_func
Inside func
2. 裝飾器與高階函數
在上面的兩個例子中把函數作為參數或者吧函數作為返回值的函數在 Python 中被統稱為是高階函數,而我們本節課的重點裝飾器本質上其實就是一個特殊的高階函數,那么它特殊在哪里呢?
通過上面的兩個例子我們可以了解到高階函數有兩個特性:
- 輸入參數是函數;
- 輸出返回值是函數。
而裝飾器(decorate)則是兩種特性都具備,也就是說裝飾器函數的參數是一個函數,返回值也是一個函數。
函數 decorate 對函數 input 的功能進行擴充,生成并返回一個新的函數 output,新的函數 output 的功能基于函數 input。裝飾器的中裝飾的含義是指:對函數 input 的功能進行裝飾 (擴充功能),得到一個新函數 output。
3. 裝飾器的用途
既然我們已經知道了裝飾器本質上就是一個函數,只不過比較特殊而已,下面我們就一起來看下裝飾器可以用在什么地方:
3.1 需求
使用 Python 編寫了 3 種排序算法:
- quick_sort,快速排序;
- bubble_sort,冒泡排序;
- select_sort,選擇排序。
現在需要對這 3 個算法進行性能評測,記錄并打印每個排序算法的執行時間。
import time
def quick_sort():
time.sleep(1)
def bubble_sort():
time.sleep(2)
def select_sort():
time.sleep(3)
引入 time 模塊,使用 time.sleep () 模擬函數的執行時間;使用 time.sleep (1) 模擬 quick_sort 的執行時間為 1 秒;使用 time.sleep (2) 模擬 bubble_sort 的執行時間為 2 秒;使用 time.sleep (3) 模擬 select_sort 的執行時間為 3 秒。
3.2 不使用裝飾器
對于這個需求,我們先不使用裝飾器,僅使用 Python 的基礎語法完成任務,如下所示:
import time
def quick_sort():
start_time = time.time()
time.sleep(1)
end_time = time.time()
print('%.2f seconds' % (end_time - start_time))
引入 time 模塊,需要使用 sleep 方法;在函數的頭部,記錄開始時間 start_time; 在函數的尾部,記錄結束時間 end_time;打印開始時間和結束時間的差,即函數的執行時間。
def bubble_sort():
start_time = time.time()
time.sleep(2)
end_time = time.time()
print('%.2f seconds' % (end_time - start_time))
def select_sort():
start_time = time.time()
time.sleep(3)
end_time = time.time()
print('%.2f seconds' % (end_time - start_time))
使用同樣的方法,對 bubble_sort 和 select_sort 進行修改。
quick_sort()
bubble_sort()
select_sort()
依次調用 quick_sort、bubble_sort、select_sort,打印它們各自的運行時間,程序輸出如下:
1.00 seconds
2.00 seconds
3.00 seconds
3.3 使用裝飾器
在上一個小節中,我們沒有使用裝飾器同樣完成了需求,但是這樣做存在一個明顯的問題:
- quick_sort、bubble_sort、select_sort 存在代碼重復;
- 在函數頭部記錄開始時間、在函數尾部記錄結束時間,邏輯是完全相同的。
而通過使用裝飾器,可以消除代碼重復,代碼如下:
import time
def quick_sort():
time.sleep(1)
def bubble_sort():
time.sleep(2)
def select_sort():
time.sleep(3)
在上一節的例子中,需要對 quick_sort、bubble_sort 和 select_sort 進行修改。在本節的例子中,不對 quick_sort、bubble_sort 和 select_sort 進行任何修改。
def decorate(input_sort):
def output_sort():
start_time = time.time()
input_sort()
end_time = time.time()
print('%.2f seconds' % (end_time - start_time))
return output_sort
裝飾器 decorate 是一個高階函數,輸入參數 input_sort 是一個排序函數,返回值是 output_sort 一個功能增強的排序函數。
在第 3 行,在 output_sort 函數的頭部,記錄開始時間,調用原排序函數 input_sort;在第 5 行,在 output_sort 函數的尾部,記錄結束時間。
quick_sort = decorate(quick_sort)
bubble_sort = decorate(bubble_sort)
select_sort = decorate(select_sort)
使用 decorate (quick_sort),生成一個功能增強的 quick_sort,并替換原有的 quick_sort;使用 decorate (bubble_sort),生成一個功能增強的 bubble_sort,并替換原有的 bubble_sort;使用 decorate (select_sort),生成一個功能增強的 select_sort,并替換原有的 select_sort。
quick_sort()
bubble_sort()
select_sort()
依次調用 quick_sort、bubble_sort、select_sort,打印它們各自的運行時間,程序輸出如下:
1.00 seconds
2.00 seconds
3.00 seconds
4. Python 的裝飾器語法
4.1 裝飾器語法
對裝飾器這樣的高階函數的應用,Python 提供了特殊的裝飾器語法,用法如下:
def decorate(input_function):
def output_function():
pass
return output_function
@decorate
def input_function():
pass
首先定義裝飾器函數 decorate,然后使用 @decorate 裝飾需要增強功能的函數 input_function。以上使用裝飾器語法 @decorate 的代碼會被翻譯如下:
def decorate(input_function):
def output_function():
pass
return output_function
def input_function():
pass
input_function = decorate(input_function)
decorate 函數接受輸入參數 input_function,返回一個功能增強的函數 output_function。 用功能增強的新函數 output_function 替換原有的舊函數 input_function。
4.2 使用裝飾器語法
在本小節,使用 Python 的裝飾器語法實現對三種排序算法的性能評測:
import time
def decorate(input_sort):
def output_sort():
start_time = time.time()
input_sort()
end_time = time.time()
print('%.2f seconds' % (end_time - start_time))
return output_sort
裝飾器 decorate 是一個高階函數,輸入參數 input_sort 是一個排序函數,返回值是 output_sort 一個功能增強的排序函數。
在 output_sort 函數的頭部,記錄開始時間,調用原排序函數 input_sort,在 output_sort 函數的尾部,記錄結束時間。
@decorate
def quick_sort():
time.sleep(1)
@decorate
def bubble_sort():
time.sleep(2)
@decorate
def select_sort():
time.sleep(3)
使用裝飾器 decorate 裝飾 quick_sort,得到一個功能增強的 quick_sort;使用裝飾器 decorate 裝飾 bubble_sort,得到一個功能增強的 bubble_sort;使用裝飾器 decorate 裝飾 select_sort,得到一個功能增強的 select_sort。
quick_sort()
bubble_sort()
select_sort()
依次調用 quick_sort、bubble_sort、select_sort,打印它們各自的運行時間,程序輸出如下:
1.00 seconds
2.00 seconds
3.00 seconds
5. 小結
本小節講解了裝飾器的原理以及裝飾器的功能,裝飾器主要用于在原有函數的基礎上增強功能,使用思維導圖概括如下: