從零開始學Flask4 -- 請求以及應用上下文

什么是上下文膜蠢?

每一段程序都有很多外部變量。只有像Add這種簡單的函數(shù)才是沒有外部變量的莉兰。一旦你的一段程序有了外部變量挑围,這段程序就不完整,不能獨立運行糖荒。你為了使他們運行杉辙,就要給所有的外部變量一個一個寫一些值進去。這些值的集合就叫上下文捶朵。

上下文作用域蜘矢?

舉個例子:你有一個應用函數(shù)返回用戶應該跳轉(zhuǎn)到的 URL 。想象它總是會跳轉(zhuǎn)到 URL 的 next 參數(shù)综看,或 HTTP referrer 品腹,或索引頁:

from flask import request, url_for

def redirect_url():
    return request.args.get('next') or \
           request.referrer or \
           url_for('index')

我們訪問了請求的對象,但是當你運行程序時候:

>>> redirect_url()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'

出現(xiàn)這個錯誤红碑,因為我們當前并沒有可以訪問的請求舞吭。所以我們需要制造一個請求并且綁定到當前的上下文。test_request_context方法為我們創(chuàng)建了一個RequestContext:

>>> ctx = app.test_request_context('/?next=http://example.com/')

可以通過兩種方式利用這個上下文:使用 with 聲明或是調(diào)用push()和pop()方法:

>>> ctx.push()

然后我們使用請求對象:

>>> redirect_url()
u'http://example.com/'

直到我們調(diào)用pop:

>>> ctx.pop()

因為請求上下文在內(nèi)部作為一個棧來維護析珊,所以你可以多次壓棧出棧羡鸥。這在實現(xiàn)內(nèi)部重定向之類的東西時很方便。

上下文如何工作的唾琼?

我們看下這段代碼:

def wsgi_app(self, environ):
    with self.request_context(environ):
        try:
            response = self.full_dispatch_request()
        except Exception, e:
            response = self.make_response(self.handle_exception(e))
        return response(environ, start_response)

request_context()方法放回一個新的RequestContext對象兄春,并結(jié)合with聲明來綁定上下文。從相同線程中被調(diào)用的一切锡溯,直到with
聲明結(jié)束前赶舆,都可以訪問全局的請求變量flask.request和其它)。

請求上下文內(nèi)部工作如同一個棧祭饭。棧頂是當前活動的請求芜茵。push把上下文添加到棧頂,pop把它移出棧倡蝙。在出棧時九串,應用的teardown_request函數(shù)也會被執(zhí)行。

另一件需要注意的事是,請求上下文被壓入棧時猪钮,并且沒有當前應用的應用上下文品山,它會自動創(chuàng)建一個 應用上下文

請求上下文

當Flask應用真正處理請求時烤低,wsgi_app(environ, start_response)被調(diào)用肘交。這個函數(shù)是按照下面的方式運行的:

def wsgi_app(environ, start_response):
    with self.request_context(environ):
        ...

在Flask中處理請求時,應用會生成一個“請求上下文”對象扑馁。整個請求的處理過程涯呻,都會在這個上下文對象中進行。這保證了請求的處理過程不被干擾腻要。
看段代碼:

# Flask v0.1
class _RequestContext(object):
    """The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    `_request_ctx_stack` and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.
    """
    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()
        self.flashes = None
    def __enter__(self):
        _request_ctx_stack.push(self)
    def __exit__(self, exc_type, exc_value, tb):
        # do not pop the request stack if we are in debug mode and an
        # exception happened.  This will allow the debugger to still
        # access the request object in the interactive shell.
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

根據(jù)_RequestContext上下文對象的定義复罐,可以發(fā)現(xiàn),在構(gòu)造這個對象的時候添加了和Flask應用相關(guān)的一些屬性:

app ——上下文對象的app屬性是當前的Flask應用雄家;

url_adapter ——上下文對象的url_adapter屬性是通過Flask應用中的Map實例構(gòu)造成一個MapAdapter實例效诅,主要功能是將請求中的URL和Map實例中的URL規(guī)則進行匹配;

request ——上下文對象的request屬性是通過Request類構(gòu)造的實例咳短,反映請求的信息填帽;

session ——上下文對象的session屬性存儲請求的會話信息;

g ——上下文對象的g屬性可以存儲全局的一些變量咙好。

flashes ——消息閃現(xiàn)的信息篡腌。
def wsgi_app(self, environ, start_response):
    with self.request_context(environ):
        # with語句中生成一個`response`對象
        ...
    return response(environ, start_response)

請求上下文對象包含了和請求處理相關(guān)的信息。同時Flask還根據(jù)werkzeug.local模塊中實現(xiàn)的一種數(shù)據(jù)結(jié)構(gòu)LocalStack用來存儲“請求上下文”對象勾效。

  • LocalStack詳解
>>> from werkzeug.local import LocalStack
>>> import threading

# 創(chuàng)建一個`LocalStack`對象
>>> local_stack = LocalStack()
# 查看local_stack中存儲的信息
>>> local_stack._local.__storage__
{}

# 定義一個函數(shù)嘹悼,這個函數(shù)可以向`LocalStack`中添加數(shù)據(jù)
>>> def worker(i):
        local_stack.push(i)

# 使用3個線程運行函數(shù)`worker`
>>> for i in range(3):
        t = threading.Thread(target=worker, args=(i,))
        t.start()

# 再次查看local_stack中存儲的信息
>>> local_stack._local.__storage__
{<greenlet.greenlet at 0x4bee5a0>: {'stack': [2]},
 <greenlet.greenlet at 0x4bee638>: {'stack': [1]},
 <greenlet.greenlet at 0x4bee6d0>: {'stack': [0]}
}

由上面的例子可以看出,存儲在LocalStack中的信息以字典的形式存在:鍵為線程/協(xié)程的標識數(shù)值层宫,值也是字典形式杨伙。每當有一個線程/協(xié)程上要將一個對象push進LocalStack棧中,會形成如上一個“鍵-值”對萌腿。這樣的一種結(jié)構(gòu)很好地實現(xiàn)了線程/協(xié)程的隔離限匣,每個線程/協(xié)程都會根據(jù)自己線程/協(xié)程的標識數(shù)值確定存儲在棧結(jié)構(gòu)中的值。

LocalStack還實現(xiàn)了push毁菱、pop米死、top等方法。其中top方法永遠指向棧頂?shù)脑刂印m數(shù)脑厥侵府斍熬€程/協(xié)程中最后被推入棧中的元素峦筒,即local_stack._local.stack-1
local模塊

應用上下文

Flask 背后的設計理念之一就是窗慎,代碼在執(zhí)行時會處于兩種不同的“狀態(tài)”(states)物喷。當Flask對象被實例化后在模塊層次上應用便開始隱式地處于應用配置狀態(tài)卤材。一直到第一個請求還是到達這種狀態(tài)才隱式地結(jié)束。當應用處于這個狀態(tài)的時候峦失,你可以認為下面的假設是成立的:

  • 程序員可以安全地修改應用對象
  • 目前還沒有處理任何請求
  • 你必須得有一個指向應用對象的引用來修改它扇丛。不會有某個神奇的代理變量指向你剛創(chuàng)建的或者正在修改的應用對象的

相反,到了第二個狀態(tài)宠进,在處理請求時晕拆,有一些其它的規(guī)則:

  • 當一個請求激活時,上下文的本地對象flask.request和其它對象等)指向當前的請求
  • 你可以在任何時間里使用任何代碼與這些對象通信

這里有一個第三種情況材蹬,有一點點差異。有時吝镣,你正在用類似請求處理時方式來與應用交互堤器,即使并沒有活動的請求。想象一下你用交互式 Python shell 與應用交互的情況末贾,或是一個命令行應用的情況闸溃。
current_app上下文本地變量就是應用上下文驅(qū)動的。

應用上下文的作用

應用上下問存在的主要原因是拱撵,在過去辉川,請求上下文被附加了一堆函數(shù),但是又沒有什么好的解決方案拴测。因為 Flask 設計的支柱之一是你可以在一個 Python 進程中擁有多個應用乓旗。

那么代碼如何找到“正確的”應用?在過去集索,我們推薦顯式地到處傳遞應用屿愚,但是這會讓我們在使用不是以這種理念設計的庫時遇到問題。

解決上述問題的常用方法是使用后面將會提到的 current_app代理對象务荆,它被綁定到當前請求的應用的引用妆距。既然無論如何在沒有請求時創(chuàng)建一個這樣的請求上下文是一個沒有必要的昂貴操作,應用上下文就被引入了函匕。

創(chuàng)建應用上下文

有兩種方式來創(chuàng)建應用上下文娱据。第一種是隱式的:無論何時當一個請求上下文被壓棧時,如果有必要的話一個應用上下文會被一起創(chuàng)建盅惜。由于這個原因中剩,你可以忽略應用上下文的存在,除非你需要它酷窥。
第二種是顯式地調(diào)用 app_context()方法:

from flask import Flask, current_app

app = Flask(__name__)
with app.app_context():
    # within this block, current_app points to app.
    print current_app.name

在配置了SERVER_NAME時咽安,應用上下文也被用于 url_for()函數(shù)。這允許你在沒有請求時生成 URL 蓬推。

應用上下文局部變量

應用上下文會在必要時被創(chuàng)建和銷毀妆棒。它不會在線程間移動,并且也不會在不同的請求之間共享。正因為如此糕珊,它是一個存儲數(shù)據(jù)庫連接信息或是別的東西的最佳位置动分。內(nèi)部的棧對象叫做 flask._app_ctx_stack。擴展可以在最頂層自由地存儲額外信息红选,想象一下它們用一個充分獨特的名字在那里存儲信息澜公,而不是在 flask.g對象里, flask.g 是留給用戶的代碼用的喇肋。

上下文用法

上下文的一個典型應用場景就是用來緩存一些我們需要在發(fā)生請求之前或者要使用的資源坟乾。舉個例子,比如數(shù)據(jù)庫連接蝶防。當我們在應用上下文中來存儲東西的時候你得選擇一個唯一的名字甚侣,這是因為應用上下文為 Flask 應用和擴展所共享。

最常見的應用就是把資源的管理分成如下兩個部分:

  • 一個緩存在上下文中的隱式資源
  • 當上下文被銷毀時重新分配基礎資源

通常來講间学,這將會有一個 get_X() 函數(shù)來創(chuàng)建資源 X 殷费,如果它還不存在的話。 存在的話就直接返回它低葫。另外還會有一個 teardown_X() 的回調(diào)函數(shù)用于銷毀資源 X 详羡。

如下是我們剛剛提到的連接數(shù)據(jù)庫的例子:

import sqlite3
from flask import g

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = connect_to_database()
    return db

@app.teardown_appcontext
def teardown_db(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

當get_db()這個函數(shù)第一次被調(diào)用的時候數(shù)據(jù)庫連接已經(jīng)被建立了。為了使得看起來更隱式一點我們可以使用 LocalProxy這個類:

from werkzeug.local import LocalProxydb = LocalProxy(get_db)

這樣的話用戶就可以直接通過訪問db來獲取數(shù)據(jù)句柄了嘿悬,db已經(jīng)在內(nèi)部完成了對get_db()的調(diào)用实柠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹊漠,隨后出現(xiàn)的幾起案子主到,更是在濱河造成了極大的恐慌,老刑警劉巖躯概,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件登钥,死亡現(xiàn)場離奇詭異,居然都是意外死亡娶靡,警方通過查閱死者的電腦和手機牧牢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姿锭,“玉大人塔鳍,你說我怎么就攤上這事∩氪耍” “怎么了轮纫?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焚鲜。 經(jīng)常有香客問我掌唾,道長放前,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任糯彬,我火速辦了婚禮凭语,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撩扒。我一直安慰自己似扔,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布搓谆。 她就那樣靜靜地躺著炒辉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泉手。 梳的紋絲不亂的頭發(fā)上辆脸,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音螃诅,去河邊找鬼。 笑死状囱,一個胖子當著我的面吹牛术裸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亭枷,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼袭艺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叨粘?” 一聲冷哼從身側(cè)響起猾编,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎升敲,沒想到半個月后答倡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡驴党,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年瘪撇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片港庄。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡倔既,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹏氧,到底是詐尸還是另有隱情渤涌,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布把还,位于F島的核電站实蓬,受9級特大地震影響茸俭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞳秽,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一瓣履、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧练俐,春花似錦袖迎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯蝉,卻和暖如春归形,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鼻由。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工暇榴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕉世。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓蔼紧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狠轻。 傳聞我的和親對象是個殘疾皇子奸例,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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