【一】路由系统基于装饰器
from flask import Flask
app = Flask(__name__)
# (1) flask 的路由系统基于装饰器
# rule : 路径
# methods : 请求方式【列表】
# endpoint :别名
# @app.route('/detail/<int:nid>',methods=['GET'],endpoint='detail')
@app.route('/index', methods=['GET'])
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
【二】转换器
- 默认转化器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
from flask import Flask
app = Flask(__name__)
# (2) 默认转换器
'''
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
'''
# 常用 string path int
# @app.route('/index/<string:name>', methods=['GET'])
@app.route('/index', methods=['GET'])
def index(name):
return 'hello world'
if __name__ == '__main__':
app.run()
【三】路由系统的本质
【1】执行流程分析
from flask import Flask
app = Flask(__name__)
# (3)路由系统的本质
# @app.route('/index', methods=['GET']) 本质上是一个装饰器
# 执行时 ---> index = @app.route('/index', methods=['GET'])(index)
@app.route('/index', methods=['GET'])
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
- 执行
@app.route('/index', methods=['GET'])
- 本质上执行了
index = @app.route('/index', methods=['GET'])(index)
- 触发了 route 方法中的
decorator
def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f # f 是视图函数 ,在这里并没有对视图函数进行额外的处理,只是加了一些参数
return decorator
- 所以本质上执行了
index = decorator(index)
【2】分析 decorator 函数
def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
- f 是视图函数 ,在这里并没有对视图函数进行额外的处理,只是加了一些参数
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
-
视图函数执行
@app.route('/index', methods=['GET'])
- endpoint 携带在 route 内
- 从 options 中弹出 endpoint ,有则弹出,无则 None
-
核心
self.add_url_rule(rule, endpoint, f, **options)
- self 就是 app 对象
app.add_url_rule('路由地址', '路由的别名', '视图函数', '其他参数')
from flask import Flask
app = Flask(__name__)
# @app.route('/index', methods=['GET'])
def index():
return 'hello world'
# 自定义注册路由和视图
app.add_url_rule('/index', 'index', index, '其他参数')
if __name__ == '__main__':
app.run()
【3】add_url_rule 的参数详解
# URL规则
rule
# 视图函数名称
view_func
# 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}为函数提供参数
defaults = None
# 名称,用于反向生成URL,即: url_for('名称')
endpoint = None
# 允许的请求方式,如:["GET", "POST"]
methods = None,
#对URL最后的 / 符号是否严格要求
strict_slashes = None
'''
@app.route('/index', strict_slashes=False)
#访问http://www.xx.com/index/ 或http://www.xx.com/index均可
@app.route('/index', strict_slashes=True)
#仅访问http://www.xx.com/index
'''
#重定向到指定地址
redirect_to = None,
'''
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
'''
#子域名访问
subdomain = None,
'''
#C:\Windows\System32\drivers\etc\hosts
127.0.0.1 www.liuqingzheng.com
127.0.0.1 admin.liuqingzheng.com
127.0.0.1 buy.liuqingzheng.com
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'liuqingzheng.com:5000'
@app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "static.your-domain.tld"
#可以传入任意的字符串,如传入的字符串为aa,显示为 aa.liuqingzheng.com
@app.route("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return username + ".your-domain.tld"
if __name__ == '__main__':
app.run()
访问:
http://www.liuqingzheng.com:5000/dynamic
http://admin.liuqingzheng.com:5000/dynamic
http://buy.liuqingzheng.com:5000/dynamic
'''
【4】 endpoint 详解
(1)自解版
- 当我们在视图函数中不传 endpoint 时,会走以下流程
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
- 触发
decorator
方法
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
- 此时 endpoint 是None
- 接着会触发
Falsk
父类中的add_url_rule
方法
def add_url_rule(
self,
rule: str,
endpoint: str | None = None,
view_func: ft.RouteCallable | None = None,
provide_automatic_options: bool | None = None,
**options: t.Any,
) -> None:
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore
options["endpoint"] = endpoint
methods = options.pop("methods", None)
- 此时会走到
endpoint = _endpoint_from_view_func(view_func)
方法
def _endpoint_from_view_func(view_func: t.Callable) -> str:
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
"""
assert view_func is not None, "expected view func if endpoint is not provided."
return view_func.__name__
-
此时的 view_func 就是我们的视图函数
- 返回 视图函数的吗,名字作为 endpoint
-
由此也就解释了为什么在使用装饰器时,需要指定 endpoint 参数
-
如果指定 endpoint 参数,就会按照我们指定的参数进行逻辑判断
-
如果不指定 endpoint 参数,每次视图函数都会将 装饰器的 inner 传入
-
所有的 endpoint 参数都是一样的,从而引发了 ennpoint 错误
-
即
def add_url_rule( self, rule: str, endpoint: str | None = None, view_func: ft.RouteCallable | None = None, provide_automatic_options: bool | None = None, **options: t.Any, ) -> None: raise NotImplementedError
-
-
也可以是用 wrapper 包装我们的装饰器
- 包装后的装饰器就是我们的函数本身,而不再是inner函数
-
(2)参考版
在Flask中,当我们在视图函数中不传递endpoint
参数时,会按照一定的流程生成默认的endpoint
。下面我们来详细解释这个过程。
- 首先,我们定义一个Flask应用和一个路由装饰器
@app.route('/')
,该装饰器将视图函数index()
与根路由'/'进行关联。
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
- 当使用装饰器时,会触发装饰器方法
decorator
,其中会获取options
中的endpoint
参数(如果存在),如果不存在则为None
。
def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
- 接着,会调用Flask父类中的
add_url_rule
方法,该方法用于将路由规则和视图函数进行关联。在这个过程中,会检查传入的endpoint
参数是否为None
,如果是None
,则会通过视图函数名称来生成默认的endpoint
。
def add_url_rule(
self,
rule: str,
endpoint: str | None = None,
view_func: ft.RouteCallable | None = None,
provide_automatic_options: bool | None = None,
**options: t.Any,
) -> None:
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore
options["endpoint"] = endpoint
methods = options.pop("methods", None)
- 在
_endpoint_from_view_func
方法中,它会根据视图函数来生成默认的endpoint
。如果视图函数为空,则会抛出异常;否则,会返回视图函数的名称作为默认的endpoint
。
def _endpoint_from_view_func(view_func: t.Callable) -> str:
assert view_func is not None, "expected view func if endpoint is not provided."
return view_func.__name__
- 这就解释了为什么在使用装饰器时,有时需要通过指定
endpoint
参数来避免endpoint
错误的发生。如果不指定endpoint
参数,每次视图函数都会将装饰器内部函数传入add_url_rule
方法作为view_func
参数,并生成相同的endpoint
。这样就会导致所有路由都具有相同的endpoint
,进而引发endpoint
冲突异常。
为了避免这种情况,我们可以使用wrapper
函数包装装饰器。这样包装后的装饰器本身就是我们定义的函数,并且不再是内部函数。通过这种方式,每个路由装饰器都会拥有自己独立的endpoint
,并且不会产生冲突。
from flask import Flask
app = Flask(__name__)
def my_decorator(f):
@app.route('/', methods=['GET'], endpoint='my_endpoint')
def wrapper():
return f()
return wrapper
@my_decorator
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
- 通过以上的解释和扩充,我们更加详细地了解了在Flask中生成默认
endpoint
的过程,并介绍了避免endpoint
冲突的方法。