當我們新建一個flask專案時,pycharm通常已經為專案定義了一個基本路由
@app.route('/')
def hello_world():
return 'Hello World!'
此時在瀏覽器中輸入地址http://127.0.0.1:5000
,頁面會顯示出"Hello World!"的字樣
如下圖所示
那麼此時在flask後臺程式中,到底發生了什麼事情呢??
在上面的例子中,可以看到對hello_world檢視函式被app.route這個有參裝假器裝飾
來看下app.route這個有參裝飾器的內部實現原理
app是Flask主程式的類例項化本專案名得到的一個物件
app = Flask(__name__)
然後呼叫app物件的route方法來裝飾hello_world檢視函式
route方法的原始碼:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
在用app.route裝飾hello_world檢視函式的時候,實際上app.route中還可以新增一些引數。
比如指定請求的方法的變數:methods=["GET","POST"]
以及指定檢視函式的endpoint,相當於Django中檢視函式的別名等
在這裡,rule引數相當於hello_world檢視函式中的"/"路徑,options引數中包含methods和endpoint等
在route裝飾器裡,返回decorator閉包函式。
在decorator閉包函式中,先從options中獲取endpoint的值,endpoint的值預設為None
然後呼叫self.add_url_rule內部方法處理傳遞的引數rule,endpoint,f等,在這裡self指的是app這個物件
檢視app物件中的add_url_rule方法:
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
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.
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
可以看到,當在檢視函式中沒有指定endpoint時,程式會呼叫_endpoint_from_view_func方法為endpoint賦值
def _endpoint_from_view_func(view_func):
assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.'
return view_func.__name__
可以看出,_endpoint_from_view_func實際上返回的就是view_func函式的函式名。
在上面的例子中,view_func指的是hello_world這個檢視函式
所以此時,在options這個字典中有一個鍵為endpoint,對應的值為view_func函式的函式名
接著,程式從options字典中彈出"methods"的值,並對methods中的每個方法轉換為大寫,如果methods沒有從程式中獲取,則預設為"GET"
接著,程式從函式中獲取"required_methods"的值,並進行去重,預設得到一個空集合
再對methods和required_methods進行"|="
操作,也就是按位或運算
|=(按位或)運算
>>> a = 15
>>> bin(a)
'0b1111'
>>> b = 100
>>> bin(b)
'0b1100100'
>>> a |= b
>>> a
111
>>> bin(a)
'0b1101111'
>>> 0b1101111
111
先把a這個十進位制數轉換成二進位制,得到1111
再把b這個十進位制數轉換成二進位制,得到1100100
對a和b的二進位制格式進行按位或運算
a 000 0111
b 110 0100
110 0111
因為a轉換成二進位制只有四位,如果要和b的二進位制格式做位運算,則必須在頭部填充0到和b的二進位制相同的長度,得到"0000111"
或運算中,只要有一個數為1,則這一位上做或運算的結果就為1
所以上面兩個數做或運算得到的二進位制數為"0b1100111"
把這個二進位制數轉換成十進位制,則為111,把111這個十進位制數賦值給a
對methods和required_methods進行按位或運算,實際上就是把required_methods的值新增到methods方法集合裡
接著程式呼叫self.url_rule_class
方法處理rule(也就是"/"),methods和options字典
得到rule這個物件,在這裡self同樣指的是app這個物件
可以看到,url_rule_class指向的是Rule這個類的記憶體地址
url_rule_class = Rule
然後用Map類例項化得到self.url_map物件,呼叫self.url_map物件中的add方法處理rule這個物件
self.url_map = Map()
分析了上面的app.route的流程,知道使用app物件的route方法裝飾rule,實際上就是執行了add_url_rule這個方法
那如果定義一個檢視函式,呼叫app物件中的add_url_rule方法來處理對應的rule,是不是也可以完成route的裝飾器功能呢
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
def login():
return "登入成功!!"
app.add_url_rule("/login",endpoint=None,view_func=login,methods=["GET"])
啟動這個專案,在瀏覽器中開啟"http://127.0.0.1:5000/login"地址,
得到的效果如下
由些我們可以知道,雖然flask的路由實現表面上是使用了route這個裝飾器,實際上內部也是呼叫app物件中的add_url_rule方法來實現,類似於Django中中路由的用法
多個路由指向同一個檢視函式
使用多個路由指向同一個檢視函式
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
def login():
return "登入成功!!"
@app.route("/index1/")
@app.route("/index2/")
def index():
return "This is the index page!!"
app.add_url_rule("/login",endpoint=None,view_func=login,methods=["GET"])
啟動專案,在瀏覽器中分別開啟http://127.0.0.1:5000/index1/
和http://127.0.0.1:5000/index2/
,可以看到前端頁面指向同一個頁面
使用正則表示式進行路由匹配
在Django中,可以有路由系統中呼叫正則表示式進行路由規則匹配,在flask中呼叫werkzeug
外掛也可以實現這個功能
from flask import Flask, render_template, redirect
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
def __init__(self,url_map,*items):
super(RegexConverter,self).__init__(url_map)
self.regex = items[0]
app = Flask(__name__)
app.debug = True
app.url_map.converters['regex'] = RegexConverter
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/user/<regex("[0-9]{4}"):user_id>')
def user1(user_id):
return "User %s" % user_id
@app.route('/user/<regex("[a-z]{4,8}"):user_id>')
def user2(user_id):
return "User %s" % user_id
@app.route("/index1/")
@app.route("/index2/")
def index():
return "This is the index page!!"
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'),404
if __name__ == '__main__':
app.run(debug=True)
啟動專案,就可以使用正則表示式進行使用者名稱的規則匹配了
在上面的例子裡,使用者名稱可以由4位數字或4到8位的小寫字母組成,還實現了404頁面的路由
當用戶輸入的路由返回狀態碼為404時,執行page_not_found這個檢視函式
用瀏覽器分別開啟包含不同型別使用者名稱的URL,可以看到實現了正則表示式進行URL匹配
例1:輸入3位數字的使用者名稱
例2:輸入4位數字的使用者名稱
例3:輸入5位數字的使用者名稱
例4:輸入3位小寫字母的使用者名稱
例5:輸入4到8位小寫字母的使用者名稱
例6:輸入9位小寫字母的使用者名稱
由此可以實現在路由中完成正則表示式的規則匹配了