Flask路由和视图
一、 路由系统
1. 路由系统基础
-
路由装饰器:
-
Flask使用装饰器
@app.route
来将URL规则绑定到视图函数上。 -
装饰器可以指定路径规则(
rule
)、请求方法(methods
)、以及别名(endpoint
)等。
-
-
转换器:
-
Flask默认支持多种路径转换器,如
int
、float
、string
等,用于将URL中的变量部分转换为不同的数据类型。 -
转换器使得URL可以动态匹配,并允许开发者定义复杂的URL规则。
-
DEFAULT_CONVERTERS = {'default': UnicodeConverter,'string': UnicodeConverter,'any': AnyConverter,'path': PathConverter,'int': IntegerConverter,'float': FloatConverter,'uuid': UUIDConverter, }
-
2. 执行流程分析
-
路由装饰器:
-
@app.route
是一个装饰器,使用它来将一个函数绑定到一个URL规则上。 -
当装饰器被触发时,它实际上执行了
index = decorator(index)
,其中decorator
是route
方法返回的内部函数。 -
decorator
函数是route
方法的内部函数,它接收视图函数f
作为参数,并使用add_url_rule
方法将视图函数注册到路由上。 -
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 freturn decorator
-
-
add_url_rule
参数:rule
:定义URL规则。view_func
:视图函数。defaults
:默认值,用于URL中没有参数但视图函数需要参数的情况。endpoint
:用于反向生成URL的名称。methods
:允许的HTTP请求方法。strict_slashes
:是否严格要求URL末尾的斜杠。redirect_to
:重定向到指定地址。subdomain
:子域名访问。
3. Endpoint
- 默认Endpoint生成:
- 如果在装饰器中没有指定
endpoint
,Flask会默认使用视图函数的名称作为endpoint
。 - 如果存在多个路由使用了相同的视图函数,但没有指定不同的
endpoint
,会导致endpoint
冲突。
- 如果在装饰器中没有指定
- 避免Endpoint冲突:
- 可以在装饰器中明确指定
endpoint
参数,以避免冲突。 - 使用自定义装饰器,通过包装视图函数来确保每个路由都有唯一的
endpoint
。
- 可以在装饰器中明确指定
二、 视图函数
1. FBV + 装饰器
-
示例
-
from functools import wraps from flask import Flaskapp = Flask(__name__)def outer(func):@wraps(func)def inner(*args, **kwargs):print('走了装饰器')return func(*args, **kwargs)return inner@app.route('/') @outer def index():return '根路径'@app.route('/home') @outer def home():return '主页'if __name__ == '__main__':app.run()
-
-
说明:为什么在FBV中使用装饰器时,需要使用
@wraps(func)
或指定endpoint
- 使用
@wraps(func)
- 保持被装饰函数的
__name__
和__doc__
属性,这样当查看inner
函数时,它看起来就像是原始的func
函数。 - 这对于调试和文档来说非常重要,因为它们依赖于函数的名称和文档字符串。
- 保持被装饰函数的
- 指定
endpoint
- 通过指定
endpoint
,可以确保即使装饰器改变了函数的元信息,也可以通过端点名称来引用这个视图函数。
- 通过指定
- 使用
2. CBV + 装饰器
-
示例
-
from functools import wraps from flask import Flask from flask.views import MethodViewapp = Flask(__name__)def outer1(func):@wraps(func)def inner(*args, **kwargs):print('走了装饰器1')return func(*args, **kwargs)return innerdef outer2(func):@wraps(func)def inner(*args, **kwargs):print('走了装饰器2')return func(*args, **kwargs)return innerclass Home(MethodView):decorators = [outer1, outer2]methods = ['GET', "POST"]def get(self):return "GET"def post(self):return "POST"app.add_url_rule('/home', endpoint='home', view_func=Home.as_view('home'))
-
-
说明
- decorators = [装饰器1,]
- 如果有多个可以直接 逗号隔开
- methods = [‘GET’, “POST”]
- 可以控制整个视图函数的请求方式
- decorators = [装饰器1,]
3. CBV源码解析
as_view
方法关键步骤:
- 检查
init_every_request
属性:- 如果
init_every_request
为True
,则每次请求都会创建类的新实例。 - 如果为
False
,则类在第一次调用as_view
时被实例化,并且这个实例在后续请求中被重用。
- 如果
- 定义视图函数
view
:- 视图函数内部调用
self.dispatch_request
来处理请求。
- 视图函数内部调用
- 支持异步操作:
- 使用
current_app.ensure_sync
确保视图函数和dispatch_request
是同步的,即使它们可能是异步的。
- 使用
- 应用装饰器:
- 如果类定义了
decorators
属性,这些装饰器会被应用到视图函数上。 - 每个装饰器通过接收视图函数作为参数并返回一个新的函数来包装视图函数。
- 如果类定义了
- 设置视图函数的元信息:
- 视图函数的
__name__
、__module__
和__doc__
属性被设置为类的对应属性,以便于调试和文档。
- 视图函数的
- 返回视图函数:
- 最终,
as_view
返回一个完全配置好的视图函数,它可以直接注册到路由中。
- 最终,
def as_view(cls, name, *class_args, **class_kwargs):# 如果init_every_request为True,则每次请求都会创建类的新实例。# 如果为False,则类在第一次调用as_view时被实例化,并且这个实例在后续请求中被重用。if cls.init_every_request:def view(**kwargs):self = view.view_class(*class_args, **class_kwargs)# 支持异步操作return current_app.ensure_sync(self.dispatch_request)(**kwargs)else:self = cls(*class_args, **class_kwargs)def view(**kwargs):# 支持异步操作return current_app.ensure_sync(self.dispatch_request)(**kwargs)# 应用装饰器if cls.decorators:view.__name__ = nameview.__module__ = cls.__module__for decorator in cls.decorators:view = decorator(view)# 设置视图函数的元信息 view.__name__ = nameview.__doc__ = cls.__doc__view.__module__ = cls.__module__view.methods = cls.methodsview.provide_automatic_options = cls.provide_automatic_optionsreturn view
dispatch_request
方法
- 获取请求方法:
- 通过
request.method.lower()
获取请求方法的字符串表示,并将其转换为小写。
- 通过
- 查找对应的方法:
- 尝试获取与请求方法同名的类方法(例如,如果请求是GET,则查找
get
方法)。 - 如果请求是HEAD,但没有对应的处理方法,它会尝试使用GET方法。
- 尝试获取与请求方法同名的类方法(例如,如果请求是GET,则查找
- 执行方法:
- 如果找到了对应的方法,它会使用
current_app.ensure_sync
来确保该方法同步执行,并传入请求的参数。
- 如果找到了对应的方法,它会使用
- 抛出异常:
- 如果没有找到对应的方法,
dispatch_request
会抛出NotImplementedError
。
- 如果没有找到对应的方法,
def dispatch_request(self, **kwargs):# 使用getattr函数获取请求方法meth = getattr(self, request.method.lower(), None)# 如果请求方法未找到,且请求方法为"HEAD",则使用getattr函数获取"get"方法if meth is None and request.method == "HEAD":meth = getattr(self, "get", None)# 断言请求方法不为空,若为空则抛出异常assert meth is not None, f"Unimplemented method {request.method!r}"return current_app.ensure_sync(meth)(**kwargs)