Flask初探二( app.route 內(nèi)部實(shí)現(xiàn))

最小的flask應(yīng)用

from flask import Flask
app = Flask(__name__)

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

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

上一篇blog 探究了flask 各個(gè)參數(shù)的作用,本篇將圍繞 @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 實(shí)際上是一個(gè)閉包, 路徑規(guī)則通過(guò)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 規(guī)則和試圖函數(shù)綁定的方式

    @app.route('/')
    def index():
                pass
    
    # 等價(jià)于
    def index():
        pass
        
    # add_url_rule(url 規(guī)則, 端點(diǎn)名, 視圖函數(shù)名)
    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)

通過(guò)裝飾器注冊(cè)路由, 一般情況下 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__

通過(guò)查看_endpoint_from_view_func方法, 可以知道endpoint = view_func.__name__, 既通過(guò)裝飾器注冊(cè)路由,一般情況下 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)

實(shí)驗(yàn)
一般情況下 view_func 是沒(méi)有methods 屬性的, 通過(guò)修改源碼方便實(shí)驗(yàn)
源碼

  # 打印端點(diǎn)
  print(endpoint + " #" * 20)
  # 在判斷之前打印methods
  print(methods)
  
  if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
  
  # 在判斷之后打印methods
  print(methods)
  # 打印端點(diǎn)
  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'

實(shí)驗(yàn)結(jié)果

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

通過(guò)上面的結(jié)果可以看出

  • 默認(rèn)情況下, 通過(guò)@app.route(路由規(guī)則) 的方式綁定視圖函數(shù), methods 初始為None
  • 再經(jīng)過(guò)if 判斷時(shí), 通過(guò)getattr 獲取view_func 的methods 屬性, 結(jié)合或 邏輯給methods 變量賦值
  • 經(jīng)過(guò)if 之后,methods 被賦值為('GET',)
  • 當(dāng)使用@app.route('/', methods=["POST"]) 或者 @app.route('/', methods=["POST", "GET"]) , 通過(guò)鍵值對(duì)的形式為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)

通過(guò)這兩句可以得出一個(gè)結(jié)論, methods 通過(guò)鍵值對(duì)形式賦值, 除了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

實(shí)驗(yàn)
源碼改造

        # 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)

結(jié)果

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'}
********************

通過(guò)實(shí)驗(yàn)可以得出,

  • 默認(rèn)情況下 required_methods = set(getattr(view_func, 'required_methods', ())) 為None,
  • provide_automatic_options 默認(rèn)為None, 如果不通過(guò)鍵值對(duì)的方式為 provide_automatic_options 傳值, provide_automatic_options = getattr(view_func, 'provide_automatic_options', None) 的值依然為None
  • methods 默認(rèn)為("GET",) , 不包含"OPTIONS", 所以 provide_automatic_options = True , required_methods.add('OPTIONS')
  • methods |= required_methods 對(duì)集合取并集, 賦值給methods , 既在以前的基礎(chǔ)上增加 OPTIONS .

分析

源碼

    # 得到一個(gè)rule 對(duì)象
    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

實(shí)驗(yàn)
源碼改動(dòng)

 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'

# 使用同一個(gè)視圖函數(shù)
app.add_url_rule("/", "index", index)

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

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

實(shí)驗(yàn)結(jié)果

# 新的視圖函數(shù)
endpoint   static
old_func is not None False
old_func != view_func True

********************
# 新的視圖函數(shù)
endpoint   index
old_func is not None False
old_func != view_func True

********************
# 使用同一個(gè)視圖函數(shù)
endpoint   index
old_func is not None True
old_func != view_func False

********************
# 新的視圖函數(shù)
endpoint   index2
old_func is not None False
old_func != view_func True

********************
  • view_func 一般情況不為None, endpoint 一般為方法名, 所以old_func 為方法的引用.
  • 如果是一個(gè)新的視圖函數(shù) static index index2 , old_func = self.view_functions.get(endpoint) ,old_func 的值為None
  • 如果不是一個(gè)新的視圖函數(shù),但使用同一個(gè)視圖函數(shù), old_func != None ,但old_func = view_func
  • self.view_functions[endpoint] = view_func 使用端點(diǎn)作為鍵,將視圖函數(shù)的引用 view_func作為值 添加到 self.view_functions

總結(jié):

  • 綁定視圖函數(shù)有兩種方式
  • endpoint : 端點(diǎn)一般為視圖函數(shù)名
  • methods : 默認(rèn)為("GET",) , 默認(rèn)為methods 添加OPTIONS 請(qǐng)求方式
  • 通過(guò)判斷self.view_functions 中有沒(méi)有以端點(diǎn)為鍵的值 以及 view_func 視圖函數(shù)的引用, 來(lái)判斷需不需要將視圖函數(shù)添加到self.view_functions 字典中

到此結(jié)? DragonFangQy 2018.6.20

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容