最小的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