Flask 原始碼閱讀筆記 開篇

guoweikuang發表於2019-03-03

Flask 是一個 Python 實現的 Web 開發微框架, 有豐富的生態資源。本文從一段官方的示例程式碼通過一步步打斷點方式解釋 Flask 內部的執行機制,在一些關鍵概念會有相關解釋,這些前提概念對整體理解 Flask框架十分重要,本文基於flask 0.1 版本進行相應的分析。


官方demo示例

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
複製程式碼

第一行import Flask 類物件,這個無需解釋。跳到第二行,使用當前模組的名字傳入Flask類中,並例項化Flask物件,我們在這個地方打個斷點,看看Flask類別裡有什麼。

Flask debug
上圖可以看出,Flask類中定義jinia_options、request_class、response_class等屬性,這裡我們不關係具體作用,先看看原始碼中Flask 是不是定義了這些屬性。

class Flask(object):
    # 省略了註釋部分
    # flask 用作請求物件的類
    request_class = Request
    # flask 用作響應物件的類
    response_class = Response
    # 靜態檔案路徑
    static_path = '/static'
    # 金鑰,用於加密 session 或其它涉及安全的東西
    secret_key = None
    #儲存session物件資料的cookie名稱
    session_cookie_name = 'session'
    # Jinja2環境的一些選項
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )
複製程式碼

這部分是初始化Flask類中預設設定的一些屬性,其實通過名字也可以大概知道每個屬性的作用。看到這個地方時是不是一臉懵逼,Request、Response 是什麼東西,有什麼作用?Jinja2 又是什麼東西? 別急,下面慢慢解釋這幾個東西的作用。

Request && Response

from werkzeug import Request as RequestBase, Response as ResponseBase

class Request(RequestBase):
    """The request object used by default in flask.  Remembers the
    matched endpoint and view arguments.

    It is what ends up as :class:`~flask.request`.  If you want to replace
    the request object used you can subclass this and set
    :attr:`~flask.Flask.request_class` to your subclass.
    """

    def __init__(self, environ):
        RequestBase.__init__(self, environ)
        self.endpoint = None     # 請求物件的端點
        self.view_args = None    # 請求檢視函式的引數

class Response(ResponseBase):
    """The response object that is used by default in flask.  Works like the
    response object from Werkzeug but is set to have a HTML mimetype by
    default.  Quite often you don't have to create this object yourself because
    :meth:`~flask.Flask.make_response` will take care of that for you.

    If you want to replace the response object used you can subclass this and
    set :attr:`~flask.Flask.request_class` to your subclass.
    """
    default_mimetype = 'text/html'
複製程式碼

通過原始碼的註釋我們可以知道,Request、Response都只是對 werkzeug 庫的Request、Response 進行了一層包裝並加入一些屬性。先說一下它們的作用:

  • Request 的作用:是 flask 預設的請求物件,用來記住匹配的endpoint(端點)和view arguments(檢視引數)
  • Response 的作用:是 flask 預設的響應物件,預設設定MIME型別預設設定為HTML(即是定義了內容型別 Content-Type 返回的型別為HTML), 預設情況下,你不用自己建立這個物件,因為下面的 make_response 函式會幫你處理。

看完上面原始碼和解釋,是不是有新的疑問了,werkzeug又是什麼?端點又是什麼概念?額,werkzeug的作用真的很大,整個框架都是基於它實現的,下面會有一個部分專門說明這個庫。說明: werkzeug 庫和 jinja2 是 flask 的兩個依賴庫,會分出一篇文章專門介紹,這篇文章重點是整個 Flask 內部的機制,建議看到對應部分,先提前去讀兩個依賴庫的文章。

接下來繼續下一步的除錯,初始化一個Flask類, 先看看 Flask 類的初始化函式:


def _get_package_path(name):
    """Returns the path to a package or cwd if that cannot be found."""
    # 獲取 模組包 路徑,被 Flask 引用
    try:
        return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
    except (KeyError, AttributeError):
        return os.getcwd()

class Flask(object):
      # 簡化版,已經去掉註釋,建議看原始碼註釋加上這個理解
     def __init__(self, package_name):
        # 設定是否開啟除錯模式,若開啟,會監視專案程式碼變化,
        # 開發伺服器過載 Flask 應用
        self.debug = False

        # 包或模組的名字,模組的名稱將會因其作為單獨應用啟動還是作為模組匯入而不同
        # Flask 才知道到哪去找模板、靜態檔案
        self.package_name = package_name

        # 根據 Flask 傳入的__name__, 找到專案的根路徑
        self.root_path = _get_package_path(self.package_name)

        # 已註冊的所有檢視函式的字典,字典的鍵是函式名稱,可以用來生成URL(url_for函式)
        # 字典的值是函式本身, 想要註冊檢視函式,可以使用 route 裝飾器 
        self.view_functions = {}

        # 所有已註冊錯誤處理程式的字典, 字典的鍵是一個整數型別(integer)的錯誤碼
        # 字典的值是對應錯誤的函式,想要註冊錯誤handler, 可以使用 errorhandler 裝飾器
        self.error_handlers = {}

        # 請求開始進入時,但還請求還沒排程前呼叫的函式列表,也就是預處理操作
        # 可用於開啟資料庫連線或獲取當前登入使用者,使用 before_route 裝飾器註冊
        self.before_request_funcs = []

        # 請求結束時呼叫的函式列表,這些函式會被傳入當前響應物件並將其修改或替換它。
        self.after_request_funcs = []
 
        # 不帶引數呼叫的函式列表,用於填充模板上下文,每個應該返回更新模板上下文的字典
        # 預設的處理器用來注入session、request和g
        self.template_context_processors = [_default_template_ctx_processor]
        
        # 使用 werkzeug 的 routing.Map, 用於給應用增加一些URL規則,
        # URL規則形成一個Map例項的過程中會生成對應的正規表示式,可以進行URL匹配
        self.url_map = Map()
        
        # 新增靜態檔案的URL對映規則
        # SharedDataMiddleware中介軟體用來為程式新增處理靜態檔案的能力
        if self.static_path is not None:
            self.url_map.add(Rule(self.static_path + '/<filename>',
                                  build_only=True, endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target   # URL路徑和實際檔案目錄(static資料夾)的對映
            })
        
        # Jinja2 環境,它通過jinja_options建立,載入器(loader)通過
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        # 將url_for, get_flashed_message 作為全域性物件填充入模板上下文中,可以在模板中呼叫它們
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )
複製程式碼

上面就是一個 Flask 例項化時所做的工作,其實就是儲存了一下配置資訊,設定了一下Jinja2 環境,並定義了一個URL 對映物件,用於對映URL 到函式之間的關係。

總結

開篇主要講了初始化一個Flask物件,內部做了什麼工作,配置了一下資訊,設定了一下Jinja2 環境,定義了一些檢視函式存放的資料結構,定義了一個Map物件用於後面儲存URL 和 檢視函式的對映關係。接下來會有更多關於werkzeug, jinja2 和 WSGI 相關文章放出來,敬請期待!!!

參考

flask文件

flask專案原始碼0.1版本

flask註釋版

相關文章