【3.0】flask之路由系统

【一】路由系统基于装饰器

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。下面我们来详细解释这个过程。

  1. 首先,我们定义一个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()
  1. 当使用装饰器时,会触发装饰器方法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
  1. 接着,会调用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)
  1. _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__
  1. 这就解释了为什么在使用装饰器时,有时需要通过指定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冲突的方法。
posted @ 2023-08-26 21:38  Chimengmeng  阅读(46)  评论(0编辑  收藏  举报