【一】路由系统基于装饰器
- 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
冲突的方法。