因为平时在公司写后端代码都是基于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的路由的话还需要看更多的源码以及去理解才行。