flask框架中應用上下文跟請求上下文是什麼意思?

我的理解是如果app中import了某個.py文件,那麼他就是應用上下文,請求上下文就是很http請求一起傳過去的參數,這樣理解對嗎?希望大神指點下


2017-03-30更新:

關於這個問題,最近寫了篇文章(Flask中的請求上下文和應用上下文 - 知乎專欄)。如果想要了解,可以查看以下~

----

最近正在看flask的源碼,試著回答一下這個問題。由於本人非計算機專業出身,而且學習編程時間也較短,其中有些表述可能不太準確,敬請諒解。

請求上下文

在flask 0.9版本之前,flask中只有「請求上下文」的概念。那什麼是請求上下文呢?

我們先回憶一下在寫flask程序的時候,經常會碰到直接調用像current_app、request、session、g等變數。這些變數看起來似乎是全局變數,但是實質上這些變數並不是真正意義上的全局變數。如果將這些變數設置為全局變數,試想一下,多個線程同時請求程序訪問這些變數時勢必會相互影響。但是如果不設置為全局變數,那在編寫代碼時每次都要顯式地傳遞這些變數也是一件非常令人頭疼的事情,而且還容易出錯。

為了解決這些問題,flask設計時採取線程隔離的思路,也就是說在一次請求的一個線程中可以將其設置為全局變數,但是僅限於請求的這個線程內部,不同線程通過「線程標識符」來區別。這樣就不會影響到其他線程的請求。flask實現線程隔離主要是使用werkzeug中的兩個類:Local和LocalProxy,這裡不再贅述,可以去看看werkzeug的源碼了解一下實現過程。

實現線程隔離後,為了在一個線程中更加方便使用這些變數,flask中還有一種堆棧的數據結構(通過werkzeug的LocalStack實現),可以處理這些變數,但是並不直接處理這些變數。假如有一個程序得到一個請求,那麼flask會將這個請求的所有相關信息進行打包,打包形成的東西就是處理請求的一個環境。flask將這種環境稱為「請求上下文」(request context),之後flask會將這個請求上下文對象放到堆棧中。

這樣,請求發生時,我們一般都會指向堆棧中的「請求上下文」對象,這樣可以通過請求上下文獲取相關對象並直接訪問,例如current_app、request、session、g。還可以通過調用對象的方法或者屬性獲取其他信息,例如request.method。等請求結束後,請求上下文會被銷毀,堆棧重新等待新的請求上下文對象被放入。

應用上下文

應用上下文的概念是在flask 0.9中增加的。

既然flask通過線程隔離的方式,將一些變數設置為線程內的「全局」可用。由於請求上下文中包含有當前應用相關的信息,那也就是說可以通過調用current_app就可以獲取請求所在的正確應用而不會導致混淆。那為什麼需要增加一個應用上下文的概念呢?

對於單應用單請求來說,使用「請求上下文」確實就可以了。然而,Flask的設計理念之一就是多應用的支持。當在一個應用的請求上下文環境中,需要嵌套處理另一個應用的相關操作時(這種情況更多的是用於測試或者在console中對多個應用進行相關處理),「請求上下文」顯然就不能很好地解決問題了,因為魔法current_app無法確定當前處理的到底是哪個應用。如何讓請求找到「正確」的應用呢?我們可能會想到,可以再增加一個請求上下文環境,並將其推入棧中。由於兩個上下文環境的運行是獨立的,不會相互干擾,所以通過調用棧頂對象的app屬性或者調用current_app(current_app一直指向棧頂的對象)也可以獲得當前上下文環境正在處理哪個應用。這種辦法在一定程度上可行,但是如果說對第二個應用的處理不涉及到相關請求,那也就無從談起「請求上下文」,更不可能建立請求上下文環境了。

為了應對這個問題,Flask中將應用相關的信息單獨拿出來,形成一個「應用上下文」對象。這個對象可以和「請求上下文」一起使用,也可以單獨拿出來使用。不過有一點需要注意的是:在創建「請求上下文」時一定要創建一個「應用上下文」對象。有了「應用上下文」對象,便可以很容易地確定當前處理哪個應用,這就是魔法`current_app`。在0.1版本中,current_app是對_request_ctx_stack.top.app的引用,而在0.9版本中current_app是對_app_ctx_stack.top.app的引用。其中_request_ctx_stack和_app_ctx_stack分別是存儲請求上下文和應用上下文的棧。

這裡舉一個多應用的例子:

假設有兩個Flask應用:app1和app2。我們假定一種情形:在請求訪問app1時,先要對app2進行一些操作,之後再處理app1內的請求。以下是一些分析過程:

  • 請求訪問app1時,app1會生成一個請求上下文對象,並且使用with語句產生一個請求上下文環境。請求處理的所有過程都會在這個上下文環境中進行。當進入這個上下文環境時,Flask會將請求上下文對象推入_request_ctx_stack這個棧中,並且生成一個對應的應用上下文對象,將其推入_app_ctx_stack棧中。_app_ctx_stack棧頂指向的是app1;
  • 當在app1的請求上下文環境中需要對app2進行操作時,為了和app1的相關操作隔離開來,可以使用with語句建立一個app2的應用上下文環境。在這個過程中,會新建一個應用上下文對象,並將其推入_app_ctx_stack棧中。_app_ctx_stack棧頂指向的是app2;
  • 當退出app2的應用上下文環境,重新進入app1的請求上下文環境時,_app_ctx_stack棧會銷毀app2的應用上下文對象,它的棧頂指向的是app1。

通過以上一個假象的例子,我們始終可以使用current_app來表示當前處理的Flask應用。

這裡貼一段flask文檔中對於請求上下文、應用上下文的說明。文檔更加的容易理解。

  • 請求上下文

If you look into how the Flask WSGI application internally works, you will
find a piece of code that looks very much like this::

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)

The method :meth:`~Flask.request_context` returns a new
:class:`~flask.ctx.RequestContext` object and uses it in combination with
the `with` statement to bind the context. Everything that is called from
the same thread from this point onwards until the end of the `with`
statement will have access to the request globals (:data:`flask.request`
and others).

The request context internally works like a stack: The topmost level on
the stack is the current active request.
:meth:`~flask.ctx.RequestContext.push` adds the context to the stack on
the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the
stack again. On popping the application"s
:func:`~flask.Flask.teardown_request` functions are also executed.

Another thing of note is that the request context will automatically also
create an :ref:`application context &` when it"s pushed and
there is no application context for that application so far.

  • 應用上下文

The main reason for the application"s context existance is that in the
past a bunch of functionality was attached to the request context in lack
of a better solution. Since one of the pillar"s of Flask"s design is that
you can have more than one application in the same Python process.

So how does the code find the 「right」 application? In the past we
recommended passing applications around explicitly, but that caused issues
with libraries that were not designed with that in mind for libraries for
which it was too inconvenient to make this work.

A common workaround for that problem was to use the
:data:`~flask.current_app` proxy later on, which was bound to the current
request"s application reference. Since however creating such a request
context is an unnecessarily expensive operation in case there is no
request around, the application context was introduced.


Flask 從客戶端收到請求時,要讓視圖函數能訪問一些對象,這樣才能處理請求。請求對象就是一個很好的例子,它封裝了客戶端發送的HTTP 請求。

要想讓視圖函數能夠訪問請求對象,一個顯而易見的方式是將其作為參數傳入視圖函數,不過這會導致程序中的每個視圖函數都增加一個參數。除了訪問請求對象,如果視圖函數在處理請求時還要訪問其他對象,情況會變得更糟。

為了避免大量可有可無的參數把視圖函數弄得一團糟,Flask 使用上下文臨時把某些對象變為全局可訪問。有了上下文,就可以寫出下面的視圖函數:

from flask import request

@app.route("/")
def index():
user_agent = request.headers.get("User-Agent")
return "&

Your browser is %s&" % user_agent

注意在這個視圖函數中我們如何把request 當作全局變數使用。事實上,request 不可能是全局變數。試想,在多線程伺服器中,多個線程同時處理不同客戶端發送的不同請求時,每個線程看到的request 對象必然不同。Falsk 使用上下文讓特定的變數在一個線程中全局可訪問,與此同時卻不會干擾其他線程。

在Flask 中有兩種上下文:程序上下文和請求上下文。Flask 在分發請求之前激活(或推送)程序和請求上下文,請求處理完成後再將其刪除。程序上下文被推送後,就可以在線程中使用current_app 和g 變數。類似地,請求上下文被推送後,就可以使用request 和session 變數。

以上內容均來自Flask Web開發:基於Python的Web應用開發實戰

上面的解釋已經很清楚了,簡而言之就是在運行時能夠訪問的一些跟應用或請求有關的對象。


current_app、g就是程序上下文

requests、session就是請求上下文

比如說current_app,在程序沒有激活的情況下直接使用

from xx import app
from flask import current_app

current_app.name

會報錯

working outside of application context

而先行執行

from xx import app
from flask import current_app

app_ctx = app.app_context()
app_ctx.push()
current_app.name
app_ctx.pop()

就不會報錯。

g有些特殊,他會在每次請求是重設

request里封裝了client發出的http請求中的內容

session用戶存儲請求之間需要「記住」的值字典


推薦閱讀:

為什麼 Flask 有那麼多的好評?
Flask表單疑問,這個name是怎麼傳進來的?
學習Flask需要什麼基礎?
關於學習Flask過程中Python虛擬環境的激活問題?
希望用flask作為中介讓python和js交互,大家有什麼比較好的實踐經驗沒?

TAG:計算機網路 | Flask | 網站後台 |