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