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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容