我叫骆驼
会点儿代码,会点儿读书
这世上的书浩如烟海
我能做的就是尽量整理分享给你
在上一节Flask项目实战第一弹中我们讲到了路由,先看下面代码回顾一下:
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里的装饰器该怎么理解,今天我们好好唠唠这个东西。
说到装饰器,我们不得不谈一个知识点:闭包。我们从代码入手,一点一点来说闭包。
Python 有一个好玩的地儿,就是 def 函数(exterior)内部可以嵌套另一个 def 函数(interior)。调用 exterior 时,若遇到 interior , 仅仅完成对于 interior 的定义,而不去运行 interior。如果 exterior return interior,那么我们可以使用 interior () 去调用 内部函数 interior 函数。
var = 0
def exterior():
var = 1
def interior():
print(var)
return interior() # 这里返回 interior 函数调用结果
exterior() # 打印 1
从上面代码和结果中可以看到,interior 打印的 var 值 并非 第一行的 var。这说明,exterior 中的嵌套变量 var 覆盖了全局变量var=0,然后 interior 中的本地变量按照引用规则,就引用了var = 1。
接下来,我们仔细想想下面这句话:
interior 作用域在函数结束后就立即失效,而exterior嵌套作用域在 interior 的函数返回后却仍然有效。
var = 0
def exterior():
var = 1
def interior():
print(var)
return interior # 这里返回 interior 函数对象
inter = exterior()
inter() # 打印 1
看完上面代码,再思考一下刚刚的话。如果还不清楚,看下图:
图解
创建一个闭包必须满足以下几点:
现在有了闭包的知识点,我们再聊聊装饰器(decorator)。我要掰开了揉碎了来说说装饰器。
刚刚接触装饰器的同学会对这个概念感到迷茫,然后你在网上(尤其 csdn)找例子或者教程,基本千篇一律,或者讲解的“点到为止”,你在看完之后,或许更迷茫了。
函数是什么
在说装饰器前,我们聊聊 Python 的函数。众所周知:在 Python 中,一切皆对象,函数是一等对象。
编程语言理论家把“一等对象”定义为满足下述条件的程序实体:
我们看这么一段程序:
def double(x: int) -> int:
return x * 2
这段代码很简单,计算了一个整数的2倍。那么我么用 dis 模块进行反编译,看看他是怎么运行的。
>>>from my_test import double
>>>from dis import dis
>>>dis(double) # 结果如下
源码行号 |
指令在函数中的偏移 |
指令符号 |
指令参数 |
实际参数值 |
2 |
0 |
LOAD_FAST |
0 |
x |
|
2 |
LOAD_CONST |
1 |
2 |
|
4 |
BINARY_MULTIRLY |
|
|
|
6 |
RETURN_VALUE |
|
|
指令符号解释:
结合反编译的结果,仔细理解一下代码的运行流程。下面我们看另外一个例子:
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)
源码行号 |
指令在函数中的偏移 |
指令符号 |
指令参数 |
实际参数值 |
10 |
0 |
LOAD_FAST |
0 |
func |
|
2 |
LOAD_FAST |
1 |
x |
|
4 |
CALL_FUNCTION |
1 |
|
|
6 |
RETURN_VALUE |
|
|
在运行过程中:出现了 CALL_FUNCTION 。结合第13行代码,仔细体会一下这句话:函数能作为参数传递给另外一个函数。
我们现在看一下闭包的执行流程
def call_func():
def double(x: int) -> int:
return x * 2
return double
dis(call_func)
里边出现了一个关键词:MAKE_FUNCTION,见名知意,创建函数。此时再回想“一等对象”所满足的条件。
说了这么多,无非是想告诉大家一个重要的东西,函数就是对象,可以被另一个函数返回,可以被赋值,也可以被调用。
其实到这里,才真是说完闭包这个东西。装饰器和闭包大同小异,下面我们接着来。
装饰器
有这种一种等价语法:
def callfunc(func):
return 1
@callfunc
def triple(x: int) -> int:
return x * 3
等价于
def callfunc(func):
return 1
def triple(x: int) -> int:
return x * 3
triple = callfunc(triple)
无论上面那种方式,我们输出的 tripre 这个对象的值都是 1
>>> print(triple)
>>> 1
所以,闭包可以写成@这种形式呢?其实,装饰器可以理解为闭包的一种,我们可以这样认为:闭包传递的是变量,而装饰器传递的是函数,除此之外没有任何区别。
我们看一个打印时间的装饰器:
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 函数的运行时间。是不是在了解完闭包之后很简单了。
装饰器的作用就是:在不改变原函数的情况下,对已有函数进行额外的功能扩展。
恭喜你,Python 技能又进一步。
回到 Flask 我们看看路由装饰器
Flask 中路由的装饰器很简单,我们以 route 为例,以下是 route 函数源码(抽离版):
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 函数就是一个装饰器,内部嗲用 add_url_rule 实现真正的路由添加。再回过头看看装饰器的作用和定义以及使用,是不是明白了许多!加油,慢就快,快就是慢。