一 路由系統
1. 在flask中配置URL和視圖函數的路由時,首先需要在main.py中實例化一個app對象:
1 from flask import Flask, render_template 2 3 app = Flask(__name__)
2. 然后通過app實例的route方法裝飾視圖函數,實現路由的配置:
1 @app.route('/') 2 def hello_world(): 3 return 'Hellow World!'
3. 所有這里需要關注在Flask類里定義的route方法,以理解Flask內部的路由配置邏輯
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實際上是一個帶參數的裝飾器,其中rule是URL規則(字符串形式),而options可以接收其他按關鍵字傳參的配置項,在上面Hello World的例子中,options應該是一個空字典。
這個裝飾器的作用是把URL規則和視圖函數交由app實例的add_url_rule方法處理,並返回被裝飾函數本身,所以在main.py中視圖函數名依然原來的視圖函數對象的引用。
4. 下一部需要關注的是add_url_rule方法的內部實現:
在add_url_rule方法里首先處理endpoint,這里endpoint可以理解為和URL規則映射的視圖函數對象
1 if endpoint is None: 2 endpoint = _endpoint_from_view_func(view_func) 3 options['endpoint'] = endpoint
由於在Hellow World例子里,endpoint是None, 這里會調用_endpoint_from_view_func方法:
1 def _endpoint_from_view_func(view_func): 2 assert view_func is not None, 'expected view func if endpoint ' \ 3 'is not provided.' 4 return view_func.__name__ # 返回被裝飾視圖函數的函數名
這里view_func就是被裝飾的視圖函數,所以endpoint就被設置成立被裝飾視圖函數的函數名
由此可見,如果用戶希望endpoint不是被裝飾視圖函數時,需要在@app.route()里以endpoint關鍵子傳參給定一個函數對象名
處理完之后,endpoint被添加到options字典中
接着add_url_rule方法繼續處理methods, 這里methods可以理解為這條URL和視圖的映射適用於那種Http請求方法:
1 methods = options.pop('methods', None) 2 if methods is None: 3 methods = getattr(view_func, 'methods', None) or ('GET',) 4 if isinstance(methods, string_types): 5 raise TypeError('Allowed methods have to be iterables of strings, ' 6 'for example: @app.route(..., methods=["POST"])') 7 methods = set(item.upper() for item in methods) # 最后methods是一個包含用戶傳入的Http請求方法,或默認GET請求方法的集合
首先從options字典里取出'methods'對應的值,在Hellow World的例子中,此時methods = None
接着,把methods設置為視圖函數的‘methods’屬性。
p.s.:看到這里使用了getattr函數,我們可以發現,app.route裝飾的視圖,並不要求是一定要定義成函數的形式,也可以定義成一個python模塊導入到main.py中,這樣以來flask的視圖系統就具有了更加靈活的擴展性。所以methods參數既可以作為app.route的關鍵字參數,也通過定義視圖的模塊中methods標量來定義。
如果app.route()沒有傳入methods參數,也沒有再視圖模塊中定義methods變量,methods默認賦值為('GET'),可見flask中路由配置默認是對應HTTP GET請求的。
Flask要求用戶傳入各個的methods方法必須是字符串形式,並且放在符合python協議的可迭代對象中,否則,會拋出異常提示,上面4 - 6行代碼都是在做這一層判斷
最后,methods變量里的元素被取出並放入集合。
至此用戶定義的URL規則和Http請求方法處理完畢。
5. 如果視圖模塊中有定義了'requeire_methods'參數,也需要處理:
1 required_methods = set(getattr(view_func, 'required_methods', ()))
required_methods的作用這里暫時先不關注,后續再介紹
6 接下來之前的處理的methods和required_methos進行並集處理,都添加到methods參數中
methods |= required_methods
7. 把處理好的URL規則和methods參數,以及options字典委托給app實例的url_rule_class方法做進一步的處理
1 rule = self.url_rule_class(rule, methods=methods, **options)
url_rule_class實際上是一個叫Rule的類,這一步如果處理通過,參數rule會接收一個Rule的實例
8. Rule這個類的__init__方法如下:
1 class Rule(RuleFactory): 2 def __init__(self, string, defaults=None, subdomain=None, methods=None, 3 build_only=False, endpoint=None, strict_slashes=None, 4 redirect_to=None, alias=False, host=None): 5 if not string.startswith('/'): 6 raise ValueError('urls must start with a leading slash') 7 self.rule = string 8 self.is_leaf = not string.endswith('/') 9 10 self.map = None 11 self.strict_slashes = strict_slashes 12 self.subdomain = subdomain 13 self.host = host 14 self.defaults = defaults 15 self.build_only = build_only 16 self.alias = alias 17 if methods is None: 18 self.methods = None 19 else: 20 if isinstance(methods, str): 21 raise TypeError('param `methods` should be `Iterable[str]`, not `str`') 22 self.methods = set([x.upper() for x in methods]) 23 if 'HEAD' not in self.methods and 'GET' in self.methods: 24 self.methods.add('HEAD') 25 self.endpoint = endpoint 26 self.redirect_to = redirect_to 27 28 if defaults: 29 self.arguments = set(map(str, defaults)) 30 else: 31 self.arguments = set() 32 self._trace = self._converters = self._regex = self._argument_weights = None
這里再回顧一下上面給__init__方法的傳入的參數:
1 rule = self.url_rule_class(rule, methods=methods, **options)
URL規則是第一個位置參數,methods以及options字典里的鍵值對,都被__init__方法按關鍵字接收
首先,如果app.route傳入的URL不是一個以'/'開頭的字符串,會拋出異常
self.is_leaf記錄URL是否沒有以‘/’結尾
然后,如果methos里有"GET"方法,而沒有"HEAD",會把'HEAD'添加進入,'HEAD'的作用會把后續筆記中分析。
這里注意到,__init__里有一個self.redirect_to = redirect_to,可能是可以直接在app.route()里設置視圖的跳轉,這個放到后面再具體分析。
可以發現,flask里把路由相關的:URL,host,適用的HTTP請求方法,endpoint視圖都保存到了Rule這個類的實例中。
9. 得到Rule的實例后,回到add_url_rule方法,繼續看對rule實例的處理:
1 self.url_map.add(rule)
這里url_map是Map類的一個實例,是在app實例化的時候綁定到app實例的,下面只需要關注Map類的add方法:
class Map:
... ...無關代碼省略
def add(self, rulefactory): """Add a new rule or factory to the map and bind it. Requires that the rule is not bound to another map. :param rulefactory: a :class:`Rule` or :class:`RuleFactory` """ for rule in rulefactory.get_rules(self): rule.bind(self) self._rules.append(rule) self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) self._remap = True
可以看到這里rulefactory可以接收Rule實例或者RuleFactory實例,RuleFactory實例對應另一種設置路由的方法。在我們這個例子里,rulefactory應該是一個Rule的實例
所以還需要進一步關注,Rule的實例的get_rules方法:
1 def get_rules(self, map): 2 yield self
get_rules方法接收兩個參數,Rule的實例,和Map的實例,我們的例子里,Map實例沒有作用,這個方法直接yield返回了Rule實例
下面繼續看rule實例的bind方法:

1 Class Rule: 2 ... ... 省略無關代碼 3 1 def bind(self, map, rebind=False): 4 2 """Bind the url to a map and create a regular expression based on 5 3 the information from the rule itself and the defaults from the map. 6 4 7 5 :internal: 8 6 """ 9 7 if self.map is not None and not rebind: 10 8 raise RuntimeError('url rule %r already bound to map %r' % 11 9 (self, self.map)) 12 10 self.map = map 13 11 if self.strict_slashes is None: 14 12 self.strict_slashes = map.strict_slashes 15 13 if self.subdomain is None: 16 14 self.subdomain = map.default_subdomain 17 15 self.compile()
這個實在判斷rule實例的map屬性是否為None,如果是None,就把map實例綁定到rule實例的map實行,否則報錯,這里就控制了一個rule實例只能跟一個map實例進行綁定。
之后會把rule實例append到這個map實例的self._rules列表中
之后這個map實例的_rules_by_endpoint屬性的會添加這樣一個鍵值對:rule.endpoint: [rule] 也就是 視圖對象:[rule實例]
至此,整個通過app,route裝飾視圖,來綁定URL和視圖映射關系的邏輯流程已經結束,此時
app實例的self.map保存的Map類實例里保存了一個:視圖對象 和 rule實例映射的鍵值對。
總結起來:
--- app.route()裝飾器
獲取URL, 視圖對象,其他opeions方法,並調用app實例的add_url_rule方法
--- add_url_rule方法:
1. 獲取app.route的methods關鍵字參數,視圖模塊里定義的methods參數等Http 請求方法
這里視圖可以是一個函數,也可以是一個python模塊
2. 把URL,視圖對象,Http請求方法,綁定到一個Rule實例(app實例的),通過app實例的url_rule_class方法。
Rule的__init__方法的其他參數來自app.route的關鍵字傳參,可以控制一些URL的匹配規則
build_only參數可以讓URL不綁定任何視圖,實現static文件夾等。
---- url_map.add
把Rule實例和app實例保存的map實例綁定。