Flask之基于route装饰器的路由系统(源码阅读解析)

一 路由系统

1. 在flask中配置URL和视图函数的路由时,首先需要在main.py中实例化一个app对象:

1 from flask import Flask, render_template
2
3 app = Flask(__name__)

2. 然后通过app实例的route方法装饰视图函数,实现路由的配置:

1 @app.route(‘/‘)
2 def hello_world():
3     return ‘Hellow World!‘

3. 所有这里需要关注在Flask类里定义的route方法,以理解Flask内部的路由配置逻辑

    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

可见app实例的route实际上是一个带参数的装饰器,其中rule是URL规则(字符串形式),而options可以接收其他按关键字传参的配置项,在上面Hello World的例子中,options应该是一个空字典。

这个装饰器的作用是把URL规则和视图函数交由app实例的add_url_rule方法处理,并返回被装饰函数本身,所以在main.py中视图函数名依然原来的视图函数对象的引用。

4. 下一部需要关注的是add_url_rule方法的内部实现:

在add_url_rule方法里首先处理endpoint,这里endpoint可以理解为和URL规则映射的视图函数对象

1         if endpoint is None:
2             endpoint = _endpoint_from_view_func(view_func)
3         options[‘endpoint‘] = endpoint

由于在Hellow World例子里,endpoint是None, 这里会调用_endpoint_from_view_func方法:

1 def _endpoint_from_view_func(view_func):
2     assert view_func is not None, ‘expected view func if endpoint ‘ 3                                   ‘is not provided.‘
4     return view_func.__name__  # 返回被装饰视图函数的函数名

这里view_func就是被装饰的视图函数,所以endpoint就被设置成立被装饰视图函数的函数名

由此可见,如果用户希望endpoint不是被装饰视图函数时,需要在@app.route()里以endpoint关键子传参给定一个函数对象名

处理完之后,endpoint被添加到options字典中

接着add_url_rule方法继续处理methods, 这里methods可以理解为这条URL和视图的映射适用于那种Http请求方法:

1 methods = options.pop(‘methods‘, None)
2 if methods is None:
3      methods = getattr(view_func, ‘methods‘, None) or (‘GET‘,)
4 if isinstance(methods, string_types):
5       raise TypeError(‘Allowed methods have to be iterables of strings, ‘
6                            ‘for example: @app.route(..., methods=["POST"])‘)
7         methods = set(item.upper() for item in methods) # 最后methods是一个包含用户传入的Http请求方法,或默认GET请求方法的集合

首先从options字典里取出‘methods‘对应的值,在Hellow World的例子中,此时methods = None

接着,把methods设置为视图函数的‘methods’属性。

p.s.:看到这里使用了getattr函数,我们可以发现,app.route装饰的视图,并不要求是一定要定义成函数的形式,也可以定义成一个python模块导入到main.py中,这样以来flask的视图系统就具有了更加灵活的扩展性。所以methods参数既可以作为app.route的关键字参数,也通过定义视图的模块中methods标量来定义。

如果app.route()没有传入methods参数,也没有再视图模块中定义methods变量,methods默认赋值为(‘GET‘),可见flask中路由配置默认是对应HTTP GET请求的。

Flask要求用户传入各个的methods方法必须是字符串形式,并且放在符合python协议的可迭代对象中,否则,会抛出异常提示,上面4 - 6行代码都是在做这一层判断

最后,methods变量里的元素被取出并放入集合。

至此用户定义的URL规则和Http请求方法处理完毕。

5. 如果视图模块中有定义了‘requeire_methods‘参数,也需要处理:

1 required_methods = set(getattr(view_func, ‘required_methods‘, ()))
required_methods的作用这里暂时先不关注,后续再介绍

6 接下来之前的处理的methods和required_methos进行并集处理,都添加到methods参数中

methods |= required_methods

7. 把处理好的URL规则和methods参数,以及options字典委托给app实例的url_rule_class方法做进一步的处理

1 rule = self.url_rule_class(rule, methods=methods, **options)

url_rule_class实际上是一个叫Rule的类,这一步如果处理通过,参数rule会接收一个Rule的实例

8. Rule这个类的__init__方法如下:

 1 class Rule(RuleFactory):
 2     def __init__(self, string, defaults=None, subdomain=None, methods=None,
 3                  build_only=False, endpoint=None, strict_slashes=None,
 4                  redirect_to=None, alias=False, host=None):
 5         if not string.startswith(‘/‘):
 6             raise ValueError(‘urls must start with a leading slash‘)
 7         self.rule = string
 8         self.is_leaf = not string.endswith(‘/‘)
 9
10         self.map = None
11         self.strict_slashes = strict_slashes
12         self.subdomain = subdomain
13         self.host = host
14         self.defaults = defaults
15         self.build_only = build_only
16         self.alias = alias
17         if methods is None:
18             self.methods = None
19         else:
20             if isinstance(methods, str):
21                 raise TypeError(‘param `methods` should be `Iterable[str]`, not `str`‘)
22             self.methods = set([x.upper() for x in methods])
23             if ‘HEAD‘ not in self.methods and ‘GET‘ in self.methods:
24                 self.methods.add(‘HEAD‘)
25         self.endpoint = endpoint
26         self.redirect_to = redirect_to
27
28         if defaults:
29             self.arguments = set(map(str, defaults))
30         else:
31             self.arguments = set()
32         self._trace = self._converters = self._regex = self._argument_weights = None

这里再回顾一下上面给__init__方法的传入的参数:

1  rule = self.url_rule_class(rule, methods=methods, **options)

URL规则是第一个位置参数,methods以及options字典里的键值对,都被__init__方法按关键字接收

首先,如果app.route传入的URL不是一个以‘/‘开头的字符串,会抛出异常

self.is_leaf记录URL是否没有以‘/’结尾 

然后,如果methos里有"GET"方法,而没有"HEAD",会把‘HEAD‘添加进入,‘HEAD‘的作用会把后续笔记中分析。

这里注意到,__init__里有一个self.redirect_to = redirect_to,可能是可以直接在app.route()里设置视图的跳转,这个放到后面再具体分析。

可以发现,flask里把路由相关的:URL,host,适用的HTTP请求方法,endpoint视图都保存到了Rule这个类的实例中。

9. 得到Rule的实例后,回到add_url_rule方法,继续看对rule实例的处理:

1 self.url_map.add(rule)

这里url_map是Map类的一个实例,是在app实例化的时候绑定到app实例的,下面只需要关注Map类的add方法:

class Map:... ...无关代码省略    def add(self, rulefactory):
        """Add a new rule or factory to the map and bind it.  Requires that the
        rule is not bound to another map.

        :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
        """
        for rule in rulefactory.get_rules(self):
            rule.bind(self)
            self._rules.append(rule)
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
        self._remap = True

可以看到这里rulefactory可以接收Rule实例或者RuleFactory实例,RuleFactory实例对应另一种设置路由的方法。在我们这个例子里,rulefactory应该是一个Rule的实例

所以还需要进一步关注,Rule的实例的get_rules方法:

1     def get_rules(self, map):
2         yield self

get_rules方法接收两个参数,Rule的实例,和Map的实例,我们的例子里,Map实例没有作用,这个方法直接yield返回了Rule实例

下面继续看rule实例的bind方法:

 1 Class Rule:
 2     ... ... 省略无关代码
 3  1     def bind(self, map, rebind=False):
 4  2         """Bind the url to a map and create a regular expression based on
 5  3         the information from the rule itself and the defaults from the map.
 6  4
 7  5         :internal:
 8  6         """
 9  7         if self.map is not None and not rebind:
10  8             raise RuntimeError(‘url rule %r already bound to map %r‘ %
11  9                                (self, self.map))
12 10         self.map = map
13 11         if self.strict_slashes is None:
14 12             self.strict_slashes = map.strict_slashes
15 13         if self.subdomain is None:
16 14             self.subdomain = map.default_subdomain
17 15         self.compile()

这个实在判断rule实例的map属性是否为None,如果是None,就把map实例绑定到rule实例的map实行,否则报错,这里就控制了一个rule实例只能跟一个map实例进行绑定。

之后会把rule实例append到这个map实例的self._rules列表中

之后这个map实例的_rules_by_endpoint属性的会添加这样一个键值对:rule.endpoint: [rule]      也就是 视图对象:[rule实例]

至此,整个通过app,route装饰视图,来绑定URL和视图映射关系的逻辑流程已经结束,此时

app实例的self.map保存的Map类实例里保存了一个:视图对象 和 rule实例映射的键值对。

总结起来:

--- app.route()装饰器

  获取URL, 视图对象,其他opeions方法,并调用app实例的add_url_rule方法

--- add_url_rule方法:

 1. 获取app.route的methods关键字参数,视图模块里定义的methods参数等Http 请求方法

    这里视图可以是一个函数,也可以是一个python模块

2. 把URL,视图对象,Http请求方法,绑定到一个Rule实例(app实例的),通过app实例的url_rule_class方法。

    Rule的__init__方法的其他参数来自app.route的关键字传参,可以控制一些URL的匹配规则

    build_only参数可以让URL不绑定任何视图,实现static文件夹等。

---- url_map.add

  把Rule实例和app实例保存的map实例绑定。

  

  

原文地址:https://www.cnblogs.com/mikellxy1990/p/8439228.html

时间: 2024-11-09 10:56:48

Flask之基于route装饰器的路由系统(源码阅读解析)的相关文章

2、消失的路由,源码的解析基础

0.目录结构 1.看完上一章,一定会发现 路由消失了,这章看一下ionic的初始化项目的源码. /* --- app.js ----*/ import {ViewChild} from '@angular/core'; import {App, Platform, MenuController} from 'ionic-angular'; import {StatusBar} from 'ionic-native'; import {HelloIonicPage} from './pages/h

基于SSM开发在线家教预约系统源码

开发环境: Windows操作系统开发工具:Eclipse+Jdk+Tomcat8+mysql数据库 注意:次项目运行Tomcat8服务器里面 次项目比较大,需要自行研究 运行效果图 源码及原文链接:https://javadao.xyz/forum.php?mod=viewthread&tid=48 原文地址:https://www.cnblogs.com/javadao/p/12358485.html

四 .Flask 模板 中间件 特殊装饰器 基础知识(使用)

一 Flask 模板 中间件 特殊装饰器 1 .Flask 模板语法直接看面效果 https://www.cnblogs.com/lovershowtime/p/11349576.html    模板  和Django模板类似 edit.html<form> asdfasdf asdfasdf asdf asdf 哈啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 </form> login.html <!DOCTYPE html> <html lang="zh-C

函数嵌套 ,名称空间与作用域 ,闭包函数 ,装饰器 ,迭代器, 生成器 三元表达式,列表解析,生成器表达式 递归与二分法, 内置函数

函数嵌套名称空间与作用域闭包函数装饰器迭代器生成器三元表达式,列表解析,生成器表达式递归与二分法内置函数--------------------------------------------函数的嵌套调用:在调用一个函数的过程中,又调用了其他函数函数的嵌套定义:在一个函数的内部,又定义另外一个函数def max(x,y): if x>y: return x else: return ydef max1(a,b,c,d): res=max(a,b) res2=max(res,c) res3=ma

直播系统源码让您看清现代播放器的架构

随着不同应用场景的增加,直播系统源码定制化功能的需求越来越强.仅仅是直播和点播之间,就存在不同的 buffer 管理.ABR 策略和缓存策略等方面的差别.这些需求催生了一系列更为底层关于多媒体操作 API 的诞生:Flash 上面的 Netstream,HTML5 上的 Media Source Extensions,以及 Android 上的 Media Codec,同时业界又出现了一个基于 HTTP 的标准流格式 MPEG-DASH.这些更高级的能力为开发者提供了更好的灵活性,让他们可以构建

SpringMVC核心分发器DispatcherServlet分析[附带源码分析]

SpringMVC核心分发器DispatcherServlet分析[附带源码分析] 目录 前言 DispatcherServlet初始化过程 DispatcherServlet处理请求过程 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html 本文将分析SpringMVC的核心分发器Dispa

基于Python的datetime模块和time模块源码阅读分析

目录 1 前言  2 datetime.pyi源码分步解析 2.1 头部定义源码分析 2.2 tzinfo类源码分析 2.3 date类源码分析 2.4 time类源码分析 2.5 timedelta类源码分析 2.6 datetime类源码分析 2.7 格式化字符串 3 time模块time.pyi源码解析 1 前言 最近工作需求上对于datetime模块中的方法调用比较多,有时还要返回指定的格式,以及大小比较等情况.发现使用Python自带的datetime模块可以很好地实现相关需求,但是对

SpringMVC源码阅读:拦截器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC拦截器的工作原理 2.源码分析 进入SpringMVC核心类DispatcherServlet的doDispatch方法,在SpringMVC源码阅读:核心分发器DispatcherServlet曾经分析过,这里再分析一遍 936行获得HandlerExec

SpringMVC源码阅读:视图解析器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成视图解析的 2.源码分析 在SpringMVC源码阅读:拦截器分析过doDispatch的运行过程,这里再分析一遍 回到DispatcherServlet类的doDispatch方法,看看doDispatch如何获取ModelAndView Hand