Flask之基于route装饰器的路由系统(源码阅读解析)
一 路由系统
1. 在flask中配置URL和视图函数的路由时,首先需要在main.py中实例化一个app对象:
from flask import Flask, render_template app = Flask(__name__)
2. 然后通过app实例的route方法装饰视图函数,实现路由的配置:
@app.route('/')
def hello_world():
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规则映射的视图函数对象
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
由于在Hellow World例子里,endpoint是None, 这里会调用_endpoint_from_view_func方法:
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请求方法:
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是一个包含用户传入的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'参数,也需要处理:
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方法做进一步的处理
rule = self.url_rule_class(rule, methods=methods, **options)
url_rule_class实际上是一个叫Rule的类,这一步如果处理通过,参数rule会接收一个Rule的实例
8. Rule这个类的__init__方法如下:
class Rule(RuleFactory):
def __init__(self, string, defaults=None, subdomain=None, methods=None,
build_only=False, endpoint=None, strict_slashes=None,
redirect_to=None, alias=False, host=None):
if not string.startswith('/'):
raise ValueError('urls must start with a leading slash')
self.rule = string
self.is_leaf = not string.endswith('/') self.map = None
self.strict_slashes = strict_slashes
self.subdomain = subdomain
self.host = host
self.defaults = defaults
self.build_only = build_only
self.alias = alias
if methods is None:
self.methods = None
else:
if isinstance(methods, str):
raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
self.methods = set([x.upper() for x in methods])
if 'HEAD' not in self.methods and 'GET' in self.methods:
self.methods.add('HEAD')
self.endpoint = endpoint
self.redirect_to = redirect_to if defaults:
self.arguments = set(map(str, defaults))
else:
self.arguments = set()
self._trace = self._converters = self._regex = self._argument_weights = None
这里再回顾一下上面给__init__方法的传入的参数:
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实例的处理:
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方法:
def get_rules(self, map):
yield self
get_rules方法接收两个参数,Rule的实例,和Map的实例,我们的例子里,Map实例没有作用,这个方法直接yield返回了Rule实例
下面继续看rule实例的bind方法:
Class Rule:
... ... 省略无关代码
1 def bind(self, map, rebind=False):
2 """Bind the url to a map and create a regular expression based on
3 the information from the rule itself and the defaults from the map.
4
5 :internal:
6 """
7 if self.map is not None and not rebind:
8 raise RuntimeError('url rule %r already bound to map %r' %
9 (self, self.map))
10 self.map = map
11 if self.strict_slashes is None:
12 self.strict_slashes = map.strict_slashes
13 if self.subdomain is None:
14 self.subdomain = map.default_subdomain
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实例绑定。
Flask之基于route装饰器的路由系统(源码阅读解析)的更多相关文章
- flask模板语言,装饰器,路由及配置
1.模板语言jinja2 Flask中默认的模板语言是Jinja2 1.0 模板传参 from flask import Flask,render_template app = Flask(__nam ...
- Flask入门 之 没有装饰器的路由
有些时候,需要一个类似路由的功能,但又不能或者不想写装饰器,这该怎么办? so easy! eg: @app.route('login') def login(): return 'hello wor ...
- 基于SSM开发在线家教预约系统源码
开发环境: Windows操作系统开发工具:Eclipse+Jdk+Tomcat8+mysql数据库 注意:次项目运行Tomcat8服务器里面 次项目比较大,需要自行研究 运行效果图 源码及原文链接: ...
- Flask(2)- 装饰器的坑及解决办法、flask中的路由/实例化配置/对象配置/蓝图/特殊装饰器(中间件、重定义错误页面)
一.装饰器的坑以及解决方法 1.使用装饰器装饰两个视图函数,代码如下 from flask import Flask, redirect, render_template, request, sess ...
- flask笔记(三)Flask 添加登陆验证装饰器报错,及解析
Flask 添加登陆验证装饰器报错,及解析 写这个之前,是想到一个需求,这个是关于之前写Flask笔记(二)中的一个知识点,路由相关 需求为 : 有一些页面必须是登陆之后才能访问的,比如Shoppin ...
- Flask框架视图多层装饰器问题
Flask中的app.route装饰器 我们知道,在flask框架中,我们的路由匹配就是通过有参装饰器来实现的,我们看一个简单的例子: from flask import Flask, render_ ...
- 21 BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类
21_BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类 BasicTaskScheduler基本任务调度器 BasicTaskScheduler基 ...
- 基于Redis缓存的Session共享(附源码)
基于Redis缓存的Session共享(附源码) 在上一篇文章中我们研究了Redis的安装及一些基本的缓存操作,今天我们就利用Redis缓存实现一个Session共享,基于.NET平台的Seesion ...
- 基于Python的datetime模块和time模块源码阅读分析
目录 1 前言 2 datetime.pyi源码分步解析 2.1 头部定义源码分析 2.2 tzinfo类源码分析 2.3 date类源码分析 2.4 time类源码分析 2.5 timedelta ...
随机推荐
- poj1830:开关问题
链接:http://poj.org/problem?id=1830 某天“佐理慧学姐”突然来问了我这道题. 诶,窝只会线性基,但是好像搞不了方案数啊…… 啃题解吧. woc!线性代数哦,就是那种我不会 ...
- 2017"百度之星"程序设计大赛 - 复赛1003&&HDU 6146 Pokémon GO【数学,递推,dp】
Pokémon GO Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- 【Java学习笔记之八】JavaBean中布尔类型使用注意事项
JavaBean是一个标准,遵循标准的Bean是一个带有属性和getters/setters方法的Java类. JavaBean的定义很简单,但是还有有一些地方需要注意,例如Bean中含有boolea ...
- [bzoj1731] [Usaco2005 dec]Layout 排队布局
差分约束系统...因为题目要求的是1和n的最大距离所以这题就跑最长路.. 对于互相反感的牛(i与j互相反感,彼此距离至少为len,i<j),就有dis[j]-dis[i]>=len.就加一 ...
- const类型变量的详细解读
const类型变量--------------------------------------int i;const int *p; --------------------------------- ...
- python 元组学习
元组用tuple表示,用,分割开,和列表类似,但是没有排序修改等高级操作.简单地说就是终态的...... >>> tuple1 = (1,2,3)>>> type( ...
- JAVA经典算法面试40题及答案
现在是3月份,也是每年开年企业公司招聘的高峰期,同时有许多的朋友也出来找工作.现在的招聘他们有时会给你出一套面试题或者智力测试题,也有的直接让你上机操作,写一段程序.算法的计算不乏出现,基于这个原因我 ...
- 浅谈event.client、event.screen与event.offset
每每看到event.client.event.screen与event.offset这几个,头都大了,今天又碰到了,特来总结下. 1.event.screenX与event.screenY. 首先,e ...
- css3渐变之线性渐变
css3定义了两种类型的渐变,即线性渐变和径向渐变.这里我要说的是线性渐变. 为了创建一个线性渐变,你必须至少定义两种颜色结点.颜色结点即你想要呈现平稳过渡的颜色.同时,你也可以设置一个起点和一个方向 ...
- include指令与include动作的区别(面试要考)
include指令: 语法格式:<%@ include file=" " ...%> 发生作用的时间:页面转换期间 包含的内容:页面的实际内容 转换成的servlet: ...