我叫駱駝
會(huì)點(diǎn)兒代碼,會(huì)點(diǎn)兒讀書
這世上的書浩如煙海
我能做的就是盡量整理分享給你
起
在上一節(jié)Flask項(xiàng)目實(shí)戰(zhàn)第一彈中我們講到了路由,先看下面代碼回顧一下:
from flask import Flask
App = Flask(__name__)
@app.route("/hello")
def hello():
return "<p>Hello World ...</p>"
if __name__ == '__main__':
app.run(load_dotenv=True)
@app.route("/hello") 就是裝飾器。交流群里小伙伴問我,Python/ target=_blank class=infotextkey>Python里的裝飾器該怎么理解,今天我們好好嘮嘮這個(gè)東西。
承
說到裝飾器,我們不得不談一個(gè)知識點(diǎn):閉包。我們從代碼入手,一點(diǎn)一點(diǎn)來說閉包。
Python 有一個(gè)好玩的地兒,就是 def 函數(shù)(exterior)內(nèi)部可以嵌套另一個(gè) def 函數(shù)(interior)。調(diào)用 exterior 時(shí),若遇到 interior , 僅僅完成對于 interior 的定義,而不去運(yùn)行 interior。如果 exterior return interior,那么我們可以使用 interior () 去調(diào)用 內(nèi)部函數(shù) interior 函數(shù)。
var = 0
def exterior():
var = 1
def interior():
print(var)
return interior() # 這里返回 interior 函數(shù)調(diào)用結(jié)果
exterior() # 打印 1
從上面代碼和結(jié)果中可以看到,interior 打印的 var 值 并非 第一行的 var。這說明,exterior 中的嵌套變量 var 覆蓋了全局變量var=0,然后 interior 中的本地變量按照引用規(guī)則,就引用了var = 1。
接下來,我們仔細(xì)想想下面這句話:
interior 作用域在函數(shù)結(jié)束后就立即失效,而exterior嵌套作用域在 interior 的函數(shù)返回后卻仍然有效。
var = 0
def exterior():
var = 1
def interior():
print(var)
return interior # 這里返回 interior 函數(shù)對象
inter = exterior()
inter() # 打印 1
看完上面代碼,再思考一下剛剛的話。如果還不清楚,看下圖:
圖解
創(chuàng)建一個(gè)閉包必須滿足以下幾點(diǎn):
- 必須有一個(gè)內(nèi)嵌函數(shù)
- 內(nèi)嵌函數(shù)必須引用外部函數(shù)中的變量
- 外部函數(shù)的返回值必須是內(nèi)嵌函數(shù)
轉(zhuǎn)
現(xiàn)在有了閉包的知識點(diǎn),我們再聊聊裝飾器(decorator)。我要掰開了揉碎了來說說裝飾器。
剛剛接觸裝飾器的同學(xué)會(huì)對這個(gè)概念感到迷茫,然后你在網(wǎng)上(尤其 csdn)找例子或者教程,基本千篇一律,或者講解的“點(diǎn)到為止”,你在看完之后,或許更迷茫了。
函數(shù)是什么
在說裝飾器前,我們聊聊 Python 的函數(shù)。眾所周知:在 Python 中,一切皆對象,函數(shù)是一等對象。
編程語言理論家把“一等對象”定義為滿足下述條件的程序?qū)嶓w:
- 在運(yùn)行時(shí)創(chuàng)建①
- 能賦值給變量或者數(shù)據(jù)結(jié)構(gòu)中的元素②
- 能作為參數(shù)傳遞給函數(shù)③
- 能作為函數(shù)的返回結(jié)果④
我們看這么一段程序:
def double(x: int) -> int:
return x * 2
這段代碼很簡單,計(jì)算了一個(gè)整數(shù)的2倍。那么我么用 dis 模塊進(jìn)行反編譯,看看他是怎么運(yùn)行的。
>>>from my_test import double
>>>from dis import dis
>>>dis(double) # 結(jié)果如下
源碼行號 |
指令在函數(shù)中的偏移 |
指令符號 |
指令參數(shù) |
實(shí)際參數(shù)值 |
2 |
0 |
LOAD_FAST |
0 |
x |
|
2 |
LOAD_CONST |
1 |
2 |
|
4 |
BINARY_MULTIRLY |
|
|
|
6 |
RETURN_VALUE |
|
|
指令符號解釋:
- LOAD_FAST :一般加載局部變量的值,也就是讀取值,用于計(jì)算或者函數(shù)調(diào)用傳參等;
- LOAD_CONST :加載 const 變量,比如數(shù)值、字符串等等;
- BINARY_MULTIRLY:見名知意,二進(jìn)制乘法
- RETURN_VALUE:返回值
結(jié)合反編譯的結(jié)果,仔細(xì)理解一下代碼的運(yùn)行流程。下面我們看另外一個(gè)例子:
def double(x: int) -> int:
return x * 2
def triple(x: int) -> int:
return x * 3
def call_func(func, x: int) -> int:
return func(x)
result = call_func(triple, 2)
print(result)
dis(call_func)
源碼行號 |
指令在函數(shù)中的偏移 |
指令符號 |
指令參數(shù) |
實(shí)際參數(shù)值 |
10 |
0 |
LOAD_FAST |
0 |
func |
|
2 |
LOAD_FAST |
1 |
x |
|
4 |
CALL_FUNCTION |
1 |
|
|
6 |
RETURN_VALUE |
|
|
在運(yùn)行過程中:出現(xiàn)了 CALL_FUNCTION 。結(jié)合第13行代碼,仔細(xì)體會(huì)一下這句話:函數(shù)能作為參數(shù)傳遞給另外一個(gè)函數(shù)。
我們現(xiàn)在看一下閉包的執(zhí)行流程
def call_func():
def double(x: int) -> int:
return x * 2
return double
dis(call_func)
里邊出現(xiàn)了一個(gè)關(guān)鍵詞:MAKE_FUNCTION,見名知意,創(chuàng)建函數(shù)。此時(shí)再回想“一等對象”所滿足的條件。
說了這么多,無非是想告訴大家一個(gè)重要的東西,函數(shù)就是對象,可以被另一個(gè)函數(shù)返回,可以被賦值,也可以被調(diào)用。
其實(shí)到這里,才真是說完閉包這個(gè)東西。裝飾器和閉包大同小異,下面我們接著來。
裝飾器
有這種一種等價(jià)語法:
def callfunc(func):
return 1
@callfunc
def triple(x: int) -> int:
return x * 3
等價(jià)于
def callfunc(func):
return 1
def triple(x: int) -> int:
return x * 3
triple = callfunc(triple)
無論上面那種方式,我們輸出的 tripre 這個(gè)對象的值都是 1
>>> print(triple)
>>> 1
所以,閉包可以寫成@這種形式呢?其實(shí),裝飾器可以理解為閉包的一種,我們可以這樣認(rèn)為:閉包傳遞的是變量,而裝飾器傳遞的是函數(shù),除此之外沒有任何區(qū)別。
我們看一個(gè)打印時(shí)間的裝飾器:
import time
def timeit(func):
def wrapper(x):
start = time.time()
ret = func(x)
print(time.time() - start)
return ret
return wrapper
@timeit
def my_func(x):
time.sleep(x)
my_func(1)
timeit 裝飾器就打印 my_func 函數(shù)的運(yùn)行時(shí)間。是不是在了解完閉包之后很簡單了。
裝飾器的作用就是:在不改變原函數(shù)的情況下,對已有函數(shù)進(jìn)行額外的功能擴(kuò)展。
恭喜你,Python 技能又進(jìn)一步。
回到 Flask 我們看看路由裝飾器
Flask 中路由的裝飾器很簡單,我們以 route 為例,以下是 route 函數(shù)源碼(抽離版):
import typing as t
def add_url_rule(rule, endpoint, f, param):
pass
def route(rule: str, **options: t.Any) -> t.Callable:
def decorator(f: t.Callable) -> t.Callable:
endpoint = options.pop("endpoint", None)
add_url_rule(rule, endpoint, f, **options)
return f
return decorator
route 函數(shù)就是一個(gè)裝飾器,內(nèi)部嗲用 add_url_rule 實(shí)現(xiàn)真正的路由添加。再回過頭看看裝飾器的作用和定義以及使用,是不是明白了許多!加油,慢就快,快就是慢。