本篇介绍 Flask 路由的基本用法,并且通过部分源代码深入浅出阐述 Flask 路由的实现机制。
路由的基本用法我们先编写一段简单代码,代码包括两个视图函数。
from flask import Flaskapp = Flask(__name__)@app.route('/')def index():return 'Index Page'@app.route('/about')def about():return 'About Page'if __name__ == '__main__':print(app.url_map)app.run()所谓路由,就是 Flask 根据客户端 request 的 URL,查找对应的视图函数 (view function),由视图函数进行处理后,返回 response 到客户端。Flask application 有两个属性来保存与路由相关的信息:
url_map: 储存 url 和 endpoint 的映射(url_map 的数据类型是 werkzeug.routing.Map)view_functions: 储存 endpoint 和 view function 的映射 (dict 类型)就上面的例子来说,当客户端请求的 URL 中,path 为 /,Flask 就用 index 视图函数进行处理,当客户端请求的 URL 中,path 为 /about, Flask 就用 about 视图函数进行处理。运行程序,在 IDE 中打印了如下信息:
Map([ about>, index>, static>])endpoint 的作用这里有两个知识点:第一个是 url 到视图函数的映射以 endpoint 来作为中介 (url -> endpoint -> view function)。为什么从 url 到视图函数的映射使用 endpoint 作为中介呢?如果不用 blueprint,endpoint 是没什么作用的。使用 blueprint 后,endpoint 就允许通过 blueprint 来进行区分。
接下来说明 endpoint 的作用,创建一个新的 Flask 工程,工程文件的结构如下:
flask-route-logic /admin /__init__.pyuser /__init__.pyapp.pyadmin 和 user 作为两个蓝图 (blueprint),用于模块化组织代码。
admin/__init__.py 的代码如下:
# admin/__init__.pyfrom flask import Blueprintadminbp = Blueprint('adminbp', __name__, url_prefix='/admin')@adminbp.route('/')def index():return 'Admin blueprint, index page'user/__init__.py 的代码如下:
# user/__init__.pyfrom flask import Blueprintuserbp = Blueprint('userbp', __name__, url_prefix='/user')@userbp.route('/')def index():return 'User blueprint, index page'app 主文件的代码如下:
from flask import Flask, url_forfrom user import userbpfrom admin import adminbpapp = Flask(__name__)app.register_blueprint(userbp)app.register_blueprint(adminbp)@app.route('/', endpoint='index')def index():return 'Index Page'if __name__ == '__main__':print(app.url_map)app.run()在代码中,一共定义了 3 个名称都为 index 的视图函数。运行后,打印的 url_map 信息如下:
Map([ adminbp.index>, userbp.index>, index>, static>])可以看到,使用 blueprint 后,3 个 index 函数,endpoint 的名称分别为 index, adminbp.index 和 userbp.index,这样使用 url_for() 函数的时候就能区分,进行反向解析了。
定位 static 文件:static endpoint第二个知识点,在本篇第一段代码中,我们定义了两个路由,为什么打印出来的 url_map 却有 3 个 rule (/, /about 和 static) 呢?这是因为 Flask 在代码中添加了一个名为 static 的 endpoint,用于 url_for() 函数定位 static文件, 比如 css, images 等等。为了便于理解,我们用示例代码来说明。我们搭建一个如下所示的工程文件结构:
flask-route-logic /static /images /demo.pngtemplates /index.htmlapp2.py在 static/imgage 文件夹下有一个图片文件,我们在 index.html 中,将使用 url_for 函数来构建一个 url,指向 demo.png 图像文件。
index.html 文件代码如下:
url_for 函数示例Below is a plus size model:app2.py 的代码:
from flask import Flask, render_templateapp = Flask(__name__)@app.route('/')def index():return render_template('index.html')if __name__ == '__main__':app.run()这里,之所以能用 url_for('static', filename='images/demo.png) ,把 static 作为 endpoint, 就是因为 Flask 为了处理静态文件而在代码中增加的一个 endpoint ( static) 的路由匹配规则。在 Flask 的源代码 __init__() 方法中,我们可以看到这样一段代码(不同版本可能稍有出入):
if self.has_static_folder:# ...(省略无关代码)self.add_url_rule(self.static_url_path + "/",endpoint="static",host=static_host,view_func=self.send_static_file,)这段代码用硬编码的方式添加了 static 这个 endpoint。
Flask 的路由通过 @route 装饰器实现,本质上是调用 add_url_urle() 方法实现的,相关代码如下:
# flask/app.pyclass Flask(_PackageBoundObject):# ...def route(self, rule, **options):def decorator(f):endpoint = options.pop("endpoint", None)self.add_url_rule(rule, endpoint, f, **options)return freturn decoratoradd_url_rule() 函数的 3 个参数是 rule, endpoint 和 view functions,其核心代码如下(我省略了无关代码和部分细节代码):
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):# 如果没有指定endpoint,则默认为view functon的函数名if endpoint is None:endpoint = _endpoint_from_view_func(view_func) # 将 endpoint 加入到 options(dict)options["endpoint"] = endpoint# methods: GET, HEAD, OPTIONS等methods = options.pop("methods", None) # 从view function获取method,没有则为GETif methods is None:methods = getattr(view_func, "methods", None) or ("GET",)# 将 methods改变为set类型methods = set(item.upper() for item in methods)# 将rule添加到url_maprule = self.url_rule_class(rule, methods=methods, **options)self.url_map.add(rule)# 添加view functionsif view_func is not None: self.view_functions[endpoint] = view_func根据上面的核心代码,我们知道,add_url_rule() 最主要做了 4 件事:
处理 endpoint: 由函数参数提供,或者默认为函数名称处理 methods (GET, HEAD, OPTIONS, POST 等)将每个匹配规则作为 rule 添加到 url_map将 endpoint 和 view function 的映射添加到 url_functions (dict)既然 route 装饰器的本质是调用 add_url_rule(),我们的代码也可以这样写:
from flask import Flaskapp = Flask(__name__)def index():return 'Index Page'def about():return 'About Page'if __name__ == '__main__':app.add_url_rule('/', 'index', index)app.add_url_rule('/about', 'about', about)print(app.url_map)app.run()另外,说明一下,Flask 路由的数据结构、路由匹配规则等,是由 werkzeug 实现的,Flask 只是使用者而已。