Flask 源碼閱讀筆記 開篇


Flask 是一個 Python 實現(xiàn)的 Web 開發(fā)微框架, 有豐富的生態(tài)資源侍芝。本文從一段官方的示例代碼通過一步步打斷點方式解釋 Flask 內(nèi)部的運行機(jī)制储笑,在一些關(guān)鍵概念會有相關(guān)解釋呐粘,這些前提概念對整體理解 Flask框架十分重要澈侠,本文基于flask 0.1 版本進(jìn)行相應(yīng)的分析打肝。


官方demo示例

from flask import Flask
app = Flask(__name__)

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

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

第一行import Flask 類對象惭蹂,這個無需解釋。跳到第二行罚缕,使用當(dāng)前模塊的名字傳入Flask類中艇纺,并實例化Flask對象,我們在這個地方打個斷點邮弹,看看Flask類別里有什么黔衡。


Flask debug

上圖可以看出,F(xiàn)lask類中定義jinia_options腌乡、request_class盟劫、response_class等屬性,這里我們不關(guān)系具體作用与纽,先看看源碼中Flask 是不是定義了這些屬性侣签。

class Flask(object):
    # 省略了注釋部分
    # flask 用作請求對象的類
    request_class = Request
    # flask 用作響應(yīng)對象的類
    response_class = Response
    # 靜態(tài)文件路徑
    static_path = '/static'
    # 密鑰塘装,用于加密 session 或其它涉及安全的東西
    secret_key = None
    #存儲session對象數(shù)據(jù)的cookie名稱
    session_cookie_name = 'session'
    # Jinja2環(huán)境的一些選項
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

這部分是初始化Flask類中默認(rèn)設(shè)置的一些屬性,其實通過名字也可以大概知道每個屬性的作用影所”碾龋看到這個地方時是不是一臉懵逼,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    # 請求視圖函數(shù)的參數(shù)

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 進(jìn)行了一層包裝并加入一些屬性无埃。先說一下它們的作用:

  • Request 的作用:是 flask 默認(rèn)的請求對象徙瓶,用來記住匹配的endpoint(端點)和view arguments(視圖參數(shù))
  • Response 的作用:是 flask 默認(rèn)的響應(yīng)對象,默認(rèn)設(shè)置MIME類型默認(rèn)設(shè)置為HTML(即是定義了內(nèi)容類型 Content-Type 返回的類型為HTML), 默認(rèn)情況下嫉称,你不用自己創(chuàng)建這個對象侦镇,因為下面的 make_response 函數(shù)會幫你處理。

看完上面源碼和解釋织阅,是不是有新的疑問了壳繁,werkzeug又是什么?端點又是什么概念荔棉?額闹炉,werkzeug的作用真的很大,整個框架都是基于它實現(xiàn)的润樱,下面會有一個部分專門說明這個庫渣触。說明: werkzeug 庫和 jinja2 是 flask 的兩個依賴庫,會分出一篇文章專門介紹壹若,這篇文章重點是整個 Flask 內(nèi)部的機(jī)制嗅钻,建議看到對應(yīng)部分,先提前去讀兩個依賴庫的文章店展。

接下來繼續(xù)下一步的調(diào)試养篓,初始化一個Flask類, 先看看 Flask 類的初始化函數(shù):


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):
      # 簡化版赂蕴,已經(jīng)去掉注釋觉至,建議看源碼注釋加上這個理解
     def __init__(self, package_name):
        # 設(shè)置是否開啟調(diào)試模式,若開啟睡腿,會監(jiān)視項目代碼變化语御,
        # 開發(fā)服務(wù)器重載 Flask 應(yīng)用
        self.debug = False

        # 包或模塊的名字,模塊的名稱將會因其作為單獨應(yīng)用啟動還是作為模塊導(dǎo)入而不同
        # Flask 才知道到哪去找模板席怪、靜態(tài)文件
        self.package_name = package_name

        # 根據(jù) Flask 傳入的__name__, 找到項目的根路徑
        self.root_path = _get_package_path(self.package_name)

        # 已注冊的所有視圖函數(shù)的字典应闯,字典的鍵是函數(shù)名稱,可以用來生成URL(url_for函數(shù))
        # 字典的值是函數(shù)本身, 想要注冊視圖函數(shù)挂捻,可以使用 route 裝飾器 
        self.view_functions = {}

        # 所有已注冊錯誤處理程序的字典, 字典的鍵是一個整數(shù)類型(integer)的錯誤碼
        # 字典的值是對應(yīng)錯誤的函數(shù)碉纺,想要注冊錯誤handler, 可以使用 errorhandler 裝飾器
        self.error_handlers = {}

        # 請求開始進(jìn)入時,但還請求還沒調(diào)度前調(diào)用的函數(shù)列表刻撒,也就是預(yù)處理操作
        # 可用于打開數(shù)據(jù)庫連接或獲取當(dāng)前登錄用戶骨田,使用 before_route 裝飾器注冊
        self.before_request_funcs = []

        # 請求結(jié)束時調(diào)用的函數(shù)列表,這些函數(shù)會被傳入當(dāng)前響應(yīng)對象并將其修改或替換它声怔。
        self.after_request_funcs = []
 
        # 不帶參數(shù)調(diào)用的函數(shù)列表态贤,用于填充模板上下文,每個應(yīng)該返回更新模板上下文的字典
        # 默認(rèn)的處理器用來注入session醋火、request和g
        self.template_context_processors = [_default_template_ctx_processor]
        
        # 使用 werkzeug 的 routing.Map, 用于給應(yīng)用增加一些URL規(guī)則悠汽,
        # URL規(guī)則形成一個Map實例的過程中會生成對應(yīng)的正則表達(dá)式,可以進(jìn)行URL匹配
        self.url_map = Map()
        
        # 添加靜態(tài)文件的URL映射規(guī)則
        # SharedDataMiddleware中間件用來為程序添加處理靜態(tài)文件的能力
        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 環(huán)境芥驳,它通過jinja_options創(chuàng)建柿冲,加載器(loader)通過
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        # 將url_for, get_flashed_message 作為全局對象填充入模板上下文中,可以在模板中調(diào)用它們
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

上面就是一個 Flask 實例化時所做的工作兆旬,其實就是保存了一下配置信息假抄,設(shè)置了一下Jinja2 環(huán)境,并定義了一個URL 映射對象丽猬,用于映射URL 到函數(shù)之間的關(guān)系宿饱。

總結(jié)

開篇主要講了初始化一個Flask對象,內(nèi)部做了什么工作宝鼓,配置了一下信息刑棵,設(shè)置了一下Jinja2 環(huán)境,定義了一些視圖函數(shù)存放的數(shù)據(jù)結(jié)構(gòu)愚铡,定義了一個Map對象用于后面保存URL 和 視圖函數(shù)的映射關(guān)系蛉签。接下來會有更多關(guān)于werkzeug, jinja2 和 WSGI 相關(guān)文章放出來,敬請期待Aち取0帷!

提示

本文會同步flask 0.1版本的代碼注釋在github, 請通過https://github.com/guoweikuang/flask-annotate 查看整個flask 0.1 的代碼注釋邑雅,目前沒有完成所有注釋

參考

flask文檔
flask項目源碼0.1版本
flask注釋版

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末片橡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子淮野,更是在濱河造成了極大的恐慌捧书,老刑警劉巖吹泡,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異经瓷,居然都是意外死亡爆哑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門舆吮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揭朝,“玉大人,你說我怎么就攤上這事色冀√陡ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵锋恬,是天一觀的道長屯换。 經(jīng)常有香客問我,道長伶氢,這世上最難降的妖魔是什么趟径? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮癣防,結(jié)果婚禮上蜗巧,老公的妹妹穿的比我還像新娘。我一直安慰自己蕾盯,他們只是感情好幕屹,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著级遭,像睡著了一般望拖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挫鸽,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天说敏,我揣著相機(jī)與錄音,去河邊找鬼丢郊。 笑死盔沫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枫匾。 我是一名探鬼主播架诞,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼干茉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沾谓,失蹤者是張志新(化名)和其女友劉穎委造,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體均驶,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡争涌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辣恋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡模软,死狀恐怖伟骨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燃异,我是刑警寧澤携狭,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站回俐,受9級特大地震影響逛腿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仅颇,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一单默、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忘瓦,春花似錦搁廓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凌停,卻和暖如春粱年,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罚拟。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工台诗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舟舒。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓拉庶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秃励。 傳聞我的和親對象是個殘疾皇子氏仗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容