一 路由系统
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):
- assert view_func is not None, 'expected view func if endpoint' \
- 'is not provided.'
- 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'参数, 也需要处理:
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__方法如下:
- 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__方法的传入的参数:
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 方法:
- def get_rules(self, map):
- yield self
get_rules 方法接收两个参数, Rule 的实例, 和 Map 的实例, 我们的例子里, Map 实例没有作用, 这个方法直接 yield 返回了 Rule 实例
下面继续看 rule 实例的 bind 方法:
Class Rule:
... ... 省略无关代码
- def bind(self, map, rebind=False):
- """Bind the url to a map and create a regular expression based on
- the information from the rule itself and the defaults from the map.
- :internal:
- """
- if self.map is not None and not rebind:
- raise RuntimeError('url rule %r already bound to map %r' %
- (self, self.map))
- self.map = map
- if self.strict_slashes is None:
- self.strict_slashes = map.strict_slashes
- if self.subdomain is None:
- self.subdomain = map.default_subdomain
- self.compile()
- View Code
这个实在判断 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