天天看點

flask源碼系列之路由Route1. app.route()2. Rule和Map3. 總結4. 參考文獻

文章目錄

  • 1. app.route()
  • 2. Rule和Map
  • 3. 總結
  • 4. 參考文獻

flask app例子,app是flask中的Flask類的執行個體對象。

# myflask.py
from flask import Flask
 
app = Flask(__name__)         #生成app執行個體
 
@app.route('/')
def index():
	return 'Hello World'

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

上述代碼中的

@app.route('/')

就是flask中的路由功能的使用。route是app的方法,其實從用法也知道了,它實際是一個裝飾器,裝飾的是視圖函數。舉例說明,它的作用就是當浏覽器發起http請求時,如果url中的path時’/‘的話,則調用index視圖函數,也就是說将’/'這個path和index這個視圖函數關聯了起來。

為對應的視圖函數指定URL,可以一對多,即一個函數對應多個URL。

由于Flask類中的許多執行個體方法都到了

setupmethod

來裝飾,那麼就先分析下這個裝飾器吧。

flask中setupmethod函數的源碼如下:

def setupmethod(f):
    """包裹一個方法使得改方法能夠實作在debug模式下檢查是否是第一次request請求,
    隻有不是第一次請求才能夠調用被裝飾的函數。
    """
    def wrapper_func(self, *args, **kwargs):
        if self.debug and self._got_first_request:
            raise AssertionError('A setup function was called after the '
                'first request was handled.  This usually indicates a bug '
                'in the application where a module was not imported '
                'and decorators or other functionality was called too late.\n'
                'To fix this make sure to import all your view modules, '
                'database models and everything related at a central place '
                'before the application starts serving requests.')
        return f(self, *args, **kwargs)
    return update_wrapper(wrapper_func, f)
           

其中update_wrapper是python中functool包中的一個方法。他的作用是

更新一個包裹(wrapper)函數,使其看起來更像被包裹(wrapped)的函數。

可選的參數指定了被包裹函數的哪些屬性直接指派給包裹函數的對應屬性,同時包裹函數的哪些屬性要更新而不是直接接受被包裹函數的對應屬性,參數assigned的預設值對應于子產品級常量WRAPPER_ASSIGNMENTS(預設地将被包裹函數的 name, module,和 doc 屬性指派給包裹函數),參數updated的預設值對應于子產品級常量WRAPPER_UPDATES(預設更新wrapper函數的 dict 屬性)。

這個函數的主要用途是在一個裝飾器中,原函數會被裝飾(包裹),裝飾器函數會傳回一個wrapper函數,如果裝飾器傳回的這個wrapper函數沒有被更新,那麼它的一些中繼資料更多的是反映wrapper函數定義的特征,無法反映wrapped函數的特性。

因為裝飾之後print(被裝飾函數.name)或print(被裝飾函數.doc)會發現這些都是裝飾器函數中的包裹函數的__name__和__doc__,而不是最初這個的函數的__name__和__doc__。

update_wrapper()的作用就是将__name__和__doc__改為最初那個被裝飾的函數的__name__和__doc__。

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """更新包裝函數(wrappper)以使其看起來像被包裝函數(wrapped)。
    :param wrapper: 需要更新的函數。
    :param wrappered: 最初的函數,也就是被包裝函數。
    :param assigned: 是一個元組。作用是直接将wrappered函數的元組中的屬性指派給wrapper函數,預設值是functools.WRAPPER_ASSIGNMENTS。
    :param updated: 是一個元組。根據wrappered函數的屬性更新wrapper函數的相應屬性。預設值是functools.WRAPPER_UPDATES
      
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper
           

,具體分析見 functools子產品

Python标準子產品–functools

1. app.route()

首先我們分下Flask類中定義的route執行個體方法。源碼如下:

def route(self, rule, **options):
    """為給定的URL規則注冊一個視圖函數的裝飾器。它的作用和add_url_rule是相同的,
    不過route是像裝飾器來使用。所謂的注冊就是将之存在定義的變量(字典or清單)中。
    例如:
        @app.route('/')
        def index():
            return 'Hello World'
    
    :param rule: 字元串類型的URL規則
    :param endpoint: 端點(flask中端點的預設值是視圖函數的函數名,如果沒有認為設定的話)。
    					要為改URL規則注冊的端點。
    :param options: options是一個字典,它會傳遞給~Werkzeug.routing.Rule這個類的執行個體對象。
        		methods: methods限定了該rule(規則)所能使用的方法,它是一個list,
        		例如'GET','POST','PUT'等。預設rule所能使用的方法隻能是'GET'
    """
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator
           

從上面的源碼能看出route()就是一個裝飾器,在調用被它裝飾的函數之前會調用self.add_url_rule(),那麼我們接下來再看add_url_rule()這個方法。源碼如下:

@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,
                 provide_automatic_options=None, **options):
    """連接配接URL規則。像route()裝飾器一樣工作。如果提供了view_func參數,
    那麼此方法會将該視圖函數注冊為endpoint參數這個端點。
    一個簡單的例子是::
        @app.route('/')
        def index():
            pass
    與下邊這個等價::
        def index():
            pass
        app.add_url_rule('/', 'index', index)
    如果view_func參數沒有提供,那麼需要将視圖函數和端點關聯起來,像下面這樣:    
    app.view_functions['index'] = index
    
    :param rule: 字元串類型的URL規則
    :param endpoint: 端點(flask中端點的預設值是視圖函數的函數名,如果沒有認為設定的話)。
    					要為改URL規則注冊的端點。
    :param view_func: 當一個伺服器請求了該endpoint是需要調用的函數。也就是這個endpoint對應的視圖函數。
    
    :param provide_automatic_options: 控制是否要自動添加'OPTIONS'請求方法。
    			也可以在添加URL規則之前設定view_func.provide_automatic_options = False來控制。
    :param options: options是一個字典,它會傳遞給~Werkzeug.routing.Rule這個類的執行個體對象。
        		methods: methods限定了該rule(規則)所能使用的方法,它是一個list,
        		例如'GET','POST','PUT'等。預設rule所能使用的方法隻能是'GET'
    """
    if endpoint is None:
    	# _endpoin_from_view_func是flask.helper的一個函數,作用是為函數傳回預設的端點(view_func.__name__)
        endpoint = _endpoint_from_view_func(view_func) # 值為view_func.__name__
    options['endpoint'] = endpoint
    methods = options.pop('methods', None)

	# 如果methods沒有給定,使用view_func種的methods,如果都不存在則使用預設值('GET',)元組。
    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) # 都轉換成大寫,并去重

    # 獲得required_methods
    required_methods = set(getattr(view_func, 'required_methods', ()))

    # 從Flask 0.8開始,view_func對象可以禁用并強制啟用automatic options處理。
    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

    # 添加required mehtods到methods中
    methods |= required_methods # 集合的并集操作
    
	# url_rule_class就是werkzeug.routing子產品中的Rule類
    rule = self.url_rule_class(rule, methods=methods, **options) # 建立Rule的一個執行個體
    rule.provide_automatic_options = provide_automatic_options

	# url_map是werkzeug.routing子產品中Map()的一個執行個體,作用主要存儲URL rule。
    self.url_map.add(rule)
    # 一個endpoint隻能對應一個視圖函數。
    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
           

接下來,讓我們管道Werkzeug.routing來交接Rule()和Map()。

2. Rule和Map

Rule繼承自RuleFactory,是以讓我們先看看RuleFactory。

class RuleFactory(object):
    """隻要你有較為複雜的URL設定,最好使用規則工廠(rule factories)來避免重複性任務。
    其中一些是内置的,其他的可以通過繼承`RuleFactory`并覆寫`get_rules`來添加。
    """
    def get_rules(self, map):
        """在RuleFactory的子類中需要覆寫這個方法,并且傳回值是rule的可疊代對象。
        """
        raise NotImplementedError()
           

implements_to_string是_compat子產品中的一個方法,作用是修改了Rule的幾個屬性值。細節有需要的同學自己去看吧。

@implements_to_string
class Rule(RuleFactory):
    """Rule代表的是一種URL模式(URL pattern)。需要注意的是除了參數'string'其他都是關鍵字參數。
    """
     pass
    
class Map(object):
    """Map類存儲着所有URL rule和一些配置參數。為了防止影響到所有的rule,有些配置僅僅存在Map執行個體中。
    除了`rules`參數,其他參數必須都是關鍵字參數。
    """
           

這是werkzeu官網中的例子:

from werkzeug.routing import Map, Rule, NotFound, RequestRedirect

url_map = Map([
    Rule('/', endpoint='blog/index'),
    Rule('/<int:year>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),
    Rule('/<int:year>/<int:month>/<int:day>/<slug>',
         endpoint='blog/show_post'),
    Rule('/about', endpoint='blog/about_me'),
    Rule('/feeds/', endpoint='blog/feeds'),
    Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
])

def application(environ, start_response):
    urls = url_map.bind_to_environ(environ)
    try:
        endpoint, args = urls.match()
    except HTTPException, e:
        return e(environ, start_response)
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Rule points to %r with arguments %r' % (endpoint, args)]
           

首先,我們建立一個Map的執行個體,存儲了許多的URL規則,這些URL rule在一個list中。

每一個rule都是一個代表URL規則的字元串和一個代表視圖函數的端點的執行個體化。多個規則可以具有相同的端點,但應該具有不同的參數以允許URL構造。

在WSGI應用程式内部,我們将url_map綁定到目前請求,該請求将傳回一個新的MapAdapter。然後,可以使用此url_map擴充卡來比對或建構目前請求的域名。

也就是說在定義URL規則的時候,需要注意:(來自 Flask中url詳解)

在定義url的時候,一定要記得在最後加一個斜杠, 如果不加斜杠,那麼在浏覽器中通路這個url的時候,如果加了斜杠,那麼就通路不到。這樣使用者體驗不好。

搜尋引擎會将不加斜杠和加斜杠的視為兩個不同的url,其實是同一個url。

通過route裝飾器(或add_url_rule),就将此URL path和視圖函數建立了一個Rule執行個體,并将此執行個體加入了url_map(是Map()的一個執行個體,作用就是存儲URL rules)

3. 總結

werkzeug中的Rule類将URL和endpoint關聯起來,Map類也就是存儲Rule類的執行個體的;

flask中的route是将endpoint和視圖函數關聯起來的。

是以最終通過URL能夠找到對應的視圖函數,這就是路由的實作。

4. 參考文獻

[1] 用盡洪荒之力學習Flask源碼

[2] Flask中url詳解