简单了解Flask路由注册原理
因为平时在公司写后端代码都是基于 Python 的 Flask 框架,作为一个全栈(沾)工程师,我觉得有必要深入理解下框架的一些简单的原理。首先我们就从路由说起,注册路由是我们平时工作最常写的东西,如果项目是 MVC 模式的话,我们更是基本每天都要注册路由来完成需求。但是,最常用的东西往往是最容易被忽略的,我们以为自己每天都在写,加上 Python 的装饰器这种魔法,我们想当然的认为自己对 Flask 框架的路由很了解。其实底层的源码很少有人看过,也没人真正想去了解其中的原理,可能这就是码农思维导致的一些弊病:只要能完成需求就行。让我们先来看看常规的注册路由的方法:
@app.route('/index/', methods=('GET', 'POST'))
def index():
pass
我们先不说让无数人高潮的装饰器,确实,这个语法糖让 Python 代码变得很优雅。我们这里不讨论装饰器的利弊。但是,同样是注册路由,Flask 还有一种方法也可以注册路由:
app.add_url_rule('/index', view_func=index)
很明显这样的代码大家也能看出来,/index 就是路由的名字,view_func 变量就是视图函数,但是这样难免让人浮想联翩,因为我们知道装饰器的魔法,所以很容易让人把这两个注册路由的方法联系在一起。于是跟所有一样,我跳进了装饰器 route 的方法里面:
# route 的源码:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
# 看到这里很多人都懂了,原来装饰器实际调用的也是 add_url_rule 方法,只不过装饰器帮你做了这些而已,add_url_rule 中 rule 函数就是我们传进去的路由名字(也叫路由规则),view_func 函数就是视图函数,至于 methods 这些都是被包括在**options 里面。源码中的注释也是写的很清楚,还给力例子让人理解,看来作者也是很暖心。既然作者那么暖男,那我们肯定不假思索地跳进去看 add_url_rule 函数的源码:
# add_url_rule 的源码:
def add_url_rule(self, rule, endpoint=None, view_func=None,
provide_automatic_options=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)
required_methods = set(getattr(view_func, 'required_methods', ()))
if provide_automatic_options is None:
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
if provide_automatic_options is None:
if 'OPTIONS' not in methods:
provide_automatic_options = True
required_methods.add('OPTIONS')
else:
provide_automatic_options = False
methods |= required_methods
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
# 其实完全可以去看源码的注释,简直不要太全面,里面详细写了各种规则以及结合例子来说明这个函数的各个参数。我们挑重要和常用的几个来说:
# endpoint
先让我们看看函数一开始的代码:
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
很明显,如果我们没有传入 endpoint 参数的话,endpoint 就是 view_func 的值,也就是视图函数的名字,然后在 options 字典中添加 endpoint 的值。
# method:
methods = options.pop('methods', None)
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)
从注释中我们可以了解到 methods 的机制是很简单的,如果代码是我们一开始写的@app.route('/index', methods=['GET', 'POST']),那么路由可以接收 get 请求和 post 请求,没有传入 methods 的话 methods = None。然后假如 methods == None, 同时,view_func 没有 methods 属性的话,那么 methods 默认设置为('GET', ). 当然,methods 不能设置为字符串类型,‘POST’可以不区分大小写。感觉源码就是在教你一样。。。。
# rule:
:param rule: the URL rule as string
注释里面有一句这样的话,意思就是传进来的路由名字是要字符串。
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
路由其实是相当复杂的,从上面的代码中可以看出来,self(Flask 核心对象,就是 app 本身)其实是有一个 url_rule_class 属性,这个属性是一个 Rule 类的实例,这段代码中把路由规则和 methods 以及其他参数都装载到这个实例中,然后再放到 url_mpa 里面。这里有点绕,建议有兴趣的可以自己跳进去看源码理解。
# 结语
路由的简单的原理大概就是这些, 如果需要深入了解 Flask 的路由的话还需要看更多的源码以及去理解才行。