0%

flask学习笔记(三)

装饰器与路由[源码剖析+使用方法]

回到上一期我们初学flask时那个最简单的例子,路由是通过app.route装饰器控制的:

1
2
3
4
5
6
7
8
9
10
from flask import Flask

app = Flask(__name__)

@app.route('/index')
def index():
return 'hello world'

if __name__ == '__main__':
app.run()

装饰器
在flask中,一般情况下路由是由装饰器来注册的,当我们配置了@app.route(‘/index’)后,访问http://127.0.0.1:5000/index 就会执行/index路径对应的index视图函数。从源码入手:

1
2
3
4
5
6
7
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方法中定义了装饰器,在理解route中的装饰器之前,我们先看看python中独有的装饰器是用来做什么的,从一个简单的例子入手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def wrapper(func):
def wrapFunction(*args, **kwargs):
print('before working')
func(*args, **kwargs)
print('after working')
return wrapFunction

@wrapper
def work(param):
print(param)

if __name__ == '__main__':
work('Monday working')

results

1
2
3
before working
Monday working
after working

在函数work前使用wrapper装饰器,结果在执行test函数前后提供了一些额外的功能。
装饰器允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。装饰器除了可以包装函数外,也有利于简化冗余代码,使代码更pythonic。
从上边这个例子可以看出装饰器确实可以做到一些东西,但我们也发现了问题:如果在被装饰器装饰的函数中执行work.__name__,输出的结果是wrapFunction而非原函数名work;如果我们在使用装饰器的过程中需要保留被装饰函数原有的一些性质,可以引入functools的wraps装饰器,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from functools import wraps
def wrapper(func):
@wraps(func)
def wrapFunction(*args, **kwargs):
print('before working')
func(*args, **kwargs)
print('after working')
return wrapFunction

@wrapper
def work(param):
print(work.__name__)
print(param)

路由
跟进app.route,定义了装饰器

1
2
3
4
5
6
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方法

1
2
3
4
rule = self.url_rule_class(rule, methods=methods, **options)  
rule.provide_automatic_options = provide_automatic_options

self.url_map.add(rule)

url_rule_class = Rule,这里首先实例化了一个Rule对象rule,rule中包含了注册的路由信息,再将注册的rule添加到url_map中,当我们请求url时,如果url在url_map中,就会根据Rule对象中的路由来返回相应的视图函数。这些功能是Werkzeug的路由模块来提供的,我们注册路由时无需过多关注路由、视图函数及其背后的数据结构,只需调用装饰器或使用app.add_url_rule方法即可,这对于快速部署轻量级应用来说是至关重要的。

1
2
3
4
5
6
7
8
9
# 装饰器方式注册路由
@app.route('/index/')
def index():
return 'hello world'

# 直接调用add_url_rule方式注册路由
def index1():
return 'hello world1'
app.add_url_rule('/index1/', view_func=index1)

路由进阶用法
视图函数绑定多个URL
访问/index 和/index1 路由都会执行视图函数index

1
2
3
4
@app.route('/index1')
@app.route('/index')
def index():
return 'hello world'

动态URL

1
2
3
4
5
# 动态路由
@app.route('/index/<username>')
def index(username):
print(type(username)) # 默认为str
return 'hello world,%s'%username
1
2
3
4
@app.route('/index/<int:id>') # 获取int类型的参数,若非int则404
@app.route('/index/<float:price>') # 获取float类型的参数,若非int则404
@app.route('/index/<path:username>') # 可识别带斜杠的字符串
@app.route('/index/<uuid:uuid>') # uuid类型(uuid.uuid4())

自定义路由动态参数

1
2
3
4
5
6
7
8
9
class MyConverter(BaseConverter):
def __init__(self, map, regex):
super().__init__(map)
self.regex = regex

app.url_map.converters['loc'] = MyConverter
@app.route("/index/<loc('[a-z]{6}'):loc>")
def fn_rule(loc):
return loc

注册路由时的一些参数
endpoint 端点
在接触endpoint前我们先学习下反向访问url

1
2
3
@app.route('/index/')
def index():
return 'hello world'

url_for(‘index’)反向获取了index这个视图函数的路由,即’/index/‘,跟进url_for,发现是根据endpoint来查找路由的,这个endpoint我们一开始并没有定义过,那是根据什么来产生endpoint的呢?答案还是在add_url_rule方法中

1
2
3
4
5
6
7
# add_url_rule
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)

# _endpoint_from_view_func
assert view_func is not None, "expected view func if endpoint is not provided."
return view_func.__name__

若没有给定endpoint,endpoint默认为view_func.__name__,即视图函数的名字。当然我们也可以在注册路由时加上endpoint的值自定义endpoint

1
2
3
4
5
@app.route('/index/',endpoint='i1')
def index():
return 'hello world'

url_for('i1')

strict_slashes 忽略斜杠
@app.route(‘/index’,strict_slashes=False) 访问/index与/index/均可找到路由
@app.route(‘/index’,strict_slashes=True) 只能访问/index找到路由

subdomain 子域名路由控制

redirect_to 路由重定向