Mercer-Lee的空间

vuePress-theme-reco Mercer-Lee的空间    2018 - 2024
Mercer-Lee的空间 Mercer-Lee的空间

Choose mode

  • dark
  • auto
  • light
TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
author-avatar

Mercer-Lee的空间

27

文章

29

标签

TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
  • 简单了解Flask路由注册原理

    • route 的源码:
      • endpoint
        • method:
          • rule:
            • 结语

            简单了解Flask路由注册原理

            vuePress-theme-reco Mercer-Lee的空间    2018 - 2024

            简单了解Flask路由注册原理


            Mercer-Lee的空间 2018-10-16 Flask Python

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