Flask初探二( app.route 内部实现)

最小的flask应用

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

上一篇blog 探究了flask 各个参数的作用,本篇将围绕 @app.route('/') 探究一下flask 做了些什么

route方法

route 源码

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  This does the same thing as :meth:`add_url_rule`
        but is intended for decorator usage::

            @app.route('/')
            def index():
                return 'Hello World'

        For more information refer to :ref:`url-route-registrations`.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
                        Starting with Flask 0.6, ``OPTIONS`` is implicitly
                        added and handled by the standard request handling.
        """

        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

route 实际上是一个闭包, 路径规则通过route 方法被rule 形参引用, 然后返回decorator 方法,所以@app.route('/') <==>@decorator , 所以 hello_world =decorator (hello_world ) <==> hello_world .
@app.route('/') 的主要作在于 endpoint = options.pop('endpoint', None) 和 self.add_url_rule(rule, endpoint, f, **options) 两句.

add_url_rule

add_url_rule 源码

    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):

        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        Internally :meth:`route` invokes :meth:`add_url_rule` so if you want
        to customize the behavior via subclassing you only need to change
        this method.

        For more information refer to :ref:`url-route-registrations`.

        .. versionchanged:: 0.2
           `view_func` parameter added.

        .. versionchanged:: 0.6
           ``OPTIONS`` is added automatically as method.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param provide_automatic_options: controls whether the ``OPTIONS``
            method should be added automatically. This can also be controlled
            by setting the ``view_func.provide_automatic_options = False``
            before adding the rule.
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
                        Starting with Flask 0.6, ``OPTIONS`` is implicitly
                        added and handled by the standard request handling.
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
        if isinstance(methods, string_types):
            raise TypeError('Allowed methods have to be iterables of strings, '
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(view_func,
                                                'provide_automatic_options', None)

        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

另一种绑定方式

从方法注释可以看到另外一种可以将url 规则和试图函数绑定的方式

    @app.route('/')
    def index():
                pass
    
    # 等价于
    def index():
        pass
        
    # add_url_rule(url 规则, 端点名, 视图函数名)
    app.add_url_rule('/', 'index', index)

分析

     if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

通过装饰器注册路由, 一般情况下 endpoint 等于None, 所以endpoint = _endpoint_from_view_func(view_func).

_endpoint_from_view_func

def _endpoint_from_view_func(view_func):
    """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__

通过查看_endpoint_from_view_func方法, 可以知道endpoint = view_func.__name__, 既通过装饰器注册路由,一般情况下 endpoint 等于方法名.

分析

  if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
  if isinstance(methods, string_types):
      raise TypeError('Allowed methods have to be iterables of strings, '
                      'for example: @app.route(..., methods=["POST"])')
  methods = set(item.upper() for item in methods)

实验
一般情况下 view_func 是没有methods 属性的, 通过修改源码方便实验
源码

  # 打印端点
  print(endpoint + " #" * 20)
  # 在判断之前打印methods
  print(methods)
  
  if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
  
  # 在判断之后打印methods
  print(methods)
  # 打印端点
  print(endpoint + " #" * 20)

  if isinstance(methods, string_types):
      raise TypeError('Allowed methods have to be iterables of strings, '
                      'for example: @app.route(..., methods=["POST"])')
  methods = set(item.upper() for item in methods)

demo.py

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


@app.route('/', methods=["POST"])
def index1():
    return 'index'


@app.route('/', methods=["POST", "GET"])
def index2():
    return 'index'

实验结果

static # # # # # # # # # # # # # # # # # # # #
None
('GET',)
static # # # # # # # # # # # # # # # # # # # #
index # # # # # # # # # # # # # # # # # # # #
None
('GET',)
index # # # # # # # # # # # # # # # # # # # #
index1 # # # # # # # # # # # # # # # # # # # #
['POST']
['POST']
index1 # # # # # # # # # # # # # # # # # # # #
index2 # # # # # # # # # # # # # # # # # # # #
['POST', 'GET']
['POST', 'GET']
index2 # # # # # # # # # # # # # # # # # # # #

通过上面的结果可以看出

  • 默认情况下, 通过@app.route(路由规则) 的方式绑定视图函数, methods 初始为None
  • 再经过if 判断时, 通过getattr 获取view_func 的methods 属性, 结合或 逻辑给methods 变量赋值
  • 经过if 之后,methods 被赋值为('GET',)
  • 当使用@app.route('/', methods=["POST"]) 或者 @app.route('/', methods=["POST", "GET"]) , 通过键值对的形式为methods 赋值, methods 不为None.

分析

if isinstance(methods, string_types):
      raise TypeError('Allowed methods have to be iterables of strings, '
                      'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)

通过这两句可以得出一个结论, methods 通过键值对形式赋值, 除了methods = "POST" 的形式之外的所有可迭代的容器都可以作为值.

分析

源码

# Methods that should always be added
required_methods = set(getattr(view_func, 'required_methods', ()))

# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
    provide_automatic_options = getattr(view_func,
                                        'provide_automatic_options', None)

if provide_automatic_options is None:
    if 'OPTIONS' not in methods:
        provide_automatic_options = True
        required_methods.add('OPTIONS')
    else:
        provide_automatic_options = False

# Add the required methods now.
methods |= required_methods

实验
源码改造

        # Methods that should always be added
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(view_func,
                                                'provide_automatic_options', None)

        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False
        # --------------------------------------
        print("methods ", methods)
        print("required_methods ", required_methods)

        # Add the required methods now.
        methods |= required_methods

        print("methods ", methods)
        print("required_methods ", required_methods) 
        print("*" * 20)

demo.py

from flask import Flask

app = Flask(__name__)


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


@app.route('/', methods=("POST", "GET"))
def index2():
    return 'index'


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

结果

methods  {'GET'}
required_methods  {'OPTIONS'}
methods  {'OPTIONS', 'GET'}
required_methods  {'OPTIONS'}
********************
methods  {'GET'}
required_methods  {'OPTIONS'}
methods  {'OPTIONS', 'GET'}
required_methods  {'OPTIONS'}
********************
methods  {'POST', 'GET'}
required_methods  {'OPTIONS'}
methods  {'POST', 'OPTIONS', 'GET'}
required_methods  {'OPTIONS'}
********************

通过实验可以得出,

  • 默认情况下 required_methods = set(getattr(view_func, 'required_methods', ())) 为None,
  • provide_automatic_options 默认为None, 如果不通过键值对的方式为 provide_automatic_options 传值, provide_automatic_options = getattr(view_func, 'provide_automatic_options', None) 的值依然为None
  • methods 默认为("GET",) , 不包含"OPTIONS", 所以 provide_automatic_options = True , required_methods.add('OPTIONS')
  • methods |= required_methods 对集合取并集, 赋值给methods , 既在以前的基础上增加 OPTIONS .

分析

源码

    # 得到一个rule 对象
    rule = self.url_rule_class(rule, methods=methods, **options)
    rule.provide_automatic_options = provide_automatic_options

    # 将rule 添加到Map 中
    self.url_map.add(rule)
    if view_func is not None:
        old_func = self.view_functions.get(endpoint)
        if old_func is not None and old_func != view_func:
            raise AssertionError('View function mapping is overwriting an '
                                 'existing endpoint function: %s' % endpoint)
        self.view_functions[endpoint] = view_func

实验
源码改动

 rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:

            print("%-10s %s" % ("endpoint", endpoint))

            old_func = self.view_functions.get(endpoint)

            print("%-20s %s" % ("old_func is not None", old_func is not None))
            print("%-20s %s" % ("old_func != view_func", old_func != view_func))
            print()
            print("*" * 20)

            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

demo.py

from flask import Flask

app = Flask(__name__)


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

# 使用同一个视图函数
app.add_url_rule("/", "index", index)

app.add_url_rule("/", "index2", index)

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

实验结果

# 新的视图函数
endpoint   static
old_func is not None False
old_func != view_func True

********************
# 新的视图函数
endpoint   index
old_func is not None False
old_func != view_func True

********************
# 使用同一个视图函数
endpoint   index
old_func is not None True
old_func != view_func False

********************
# 新的视图函数
endpoint   index2
old_func is not None False
old_func != view_func True

********************
  • view_func 一般情况不为None, endpoint 一般为方法名, 所以old_func 为方法的引用.
  • 如果是一个新的视图函数 static index index2 , old_func = self.view_functions.get(endpoint) ,old_func 的值为None
  • 如果不是一个新的视图函数,但使用同一个视图函数, old_func != None ,但old_func = view_func
  • self.view_functions[endpoint] = view_func 使用端点作为键,将视图函数的引用 view_func作为值 添加到 self.view_functions

总结:

  • 绑定视图函数有两种方式
  • endpoint : 端点一般为视图函数名
  • methods : 默认为("GET",) , 默认为methods 添加OPTIONS 请求方式
  • 通过判断self.view_functions 中有没有以端点为键的值 以及 view_func 视图函数的引用, 来判断需不需要将视图函数添加到self.view_functions 字典中

到此结  DragonFangQy 2018.6.20

你可能感兴趣的:(Flask初探二( app.route 内部实现))