摘要:并且棧頂的元素都是的請求上下文和應用上下文之后,我們再在這個環境中嵌套的應用上下文。這時查看兩個棧的內容,發現兩個棧中只有的請求的請求上下文對象和應用上下文對象。而等一直指向棧頂的請求上下文對象,分別引用請求上下文的和。
在Flask中處理請求時,應用會生成一個“請求上下文”對象。整個請求的處理過程,都會在這個上下文對象中進行。這保證了請求的處理過程不被干擾。處理請求的具體代碼如下:
def wsgi_app(self, environ, start_response): with self.request_context(environ): # with語句中生成一個`response`對象 ... return response(environ, start_response)
在Flask 0.9版本之前,應用只有“請求上下文”對象,它包含了和請求處理相關的信息。同時Flask還根據werkzeug.local模塊中實現的一種數據結構LocalStack用來存儲“請求上下文”對象。這在{% post_link 一個Flask應用運行過程剖析 一個Flask應用運行過程剖析 %}中有所介紹。在0.9版本中,Flask又引入了“應用上下文”的概念。本文主要Flask中的這兩個“上下文”對象。
LocalStack在介紹“請求上下文”和“應用上下文”之前,我們對LocalStack簡要做一個回顧。在Werkzeug庫——local模塊一文中,我們講解了werkzeug.local模塊中實現的三個類Local、LocalStack和LocalProxy。關于它們的概念和詳細介紹,可以查看上面的文章。這里,我們用一個例子來說明Flask中使用的一種數據結構LocalStack。
>>> from werkzeug.local import LocalStack >>> import threading # 創建一個`LocalStack`對象 >>> local_stack = LocalStack() # 查看local_stack中存儲的信息 >>> local_stack._local.__storage__ {} # 定義一個函數,這個函數可以向`LocalStack`中添加數據 >>> def worker(i): local_stack.push(i) # 使用3個線程運行函數`worker` >>> for i in range(3): t = threading.Thread(target=worker, args=(i,)) t.start() # 再次查看local_stack中存儲的信息 >>> local_stack._local.__storage__ {: {"stack": [2]}, : {"stack": [1]}, : {"stack": [0]} }
由上面的例子可以看出,存儲在LocalStack中的信息以字典的形式存在:鍵為線程/協程的標識數值,值也是字典形式。每當有一個線程/協程上要將一個對象push進LocalStack棧中,會形成如上一個“鍵-值”對。這樣的一種結構很好地實現了線程/協程的隔離,每個線程/協程都會根據自己線程/協程的標識數值確定存儲在棧結構中的值。
LocalStack還實現了push、pop、top等方法。其中top方法永遠指向棧頂的元素。棧頂的元素是指當前線程/協程中最后被推入棧中的元素,即local_stack._local.stack[-1](注意,是stack鍵對應的對象中最后被推入的元素)。
請求上下文Flask中所有的請求處理都在“請求上下文”中進行,在它設計之初便就有這個概念。由于0.9版本代碼比較復雜,這里還是以0.1版本的代碼為例進行說明。本質上這兩個版本的“請求上下文”的運行原理沒有變化,只是新版本增加了一些功能,這點在后面再進行解釋。
請求上下文——0.1版本# 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()
由上面“請求上下文”的實現可知:
“請求上下文”是一個上下文對象,實現了__enter__和__exit__方法。可以使用with語句構造一個上下文環境。
進入上下文環境時,_request_ctx_stack這個棧中會推入一個_RequestContext對象。這個棧結構就是上面講的LocalStack棧。
推入棧中的_RequestContext對象有一些屬性,包含了請求的的所有相關信息。例如app、request、session、g、flashes。還有一個url_adapter,這個對象可以進行URL匹配。
在with語句構造的上下文環境中可以進行請求處理。當退出上下文環境時,_request_ctx_stack這個棧會銷毀剛才存儲的上下文對象。
以上的運行邏輯使得請求的處理始終在一個上下文環境中,這保證了請求處理過程不被干擾,而且請求上下文對象保存在LocalStack棧中,也很好地實現了線程/協程的隔離。
以下是一個簡單的例子:
# example - Flask v0.1 >>> from flask import Flask, _request_ctx_stack >>> import threading >>> app = Flask(__name__) # 先觀察_request_ctx_stack中包含的信息 >>> _request_ctx_stack._local.__storage__ {} # 創建一個函數,用于向棧中推入請求上下文 # 本例中不使用`with`語句 >>> def worker(): # 使用應用的test_request_context()方法創建請求上下文 request_context = app.test_request_context() _request_ctx_stack.push(request_context) # 創建3個進程分別執行worker方法 >>> for i in range(3): t = threading.Thread(target=worker) t.start() # 再觀察_request_ctx_stack中包含的信息 >>> _request_ctx_stack._local.__storage__ {: {"stack": [ ]}, : {"stack": [ ]}, : {"stack": [ ]} }
上面的結果顯示:_request_ctx_stack中為每一個線程創建了一個“鍵-值”對,每一“鍵-值”對中包含一個請求上下文對象。如果使用with語句,在離開上下文環境時棧中銷毀存儲的上下文對象信息。
請求上下文——0.9版本在0.9版本中,Flask引入了“應用上下文”的概念,這對“請求上下文”的實現有一定的改變。這個版本的“請求上下文”也是一個上下文對象。在使用with語句進入上下文環境后,_request_ctx_stack會存儲這個上下文對象。不過與0.1版本相比,有以下幾點改變:
請求上下文實現了push、pop方法,這使得對于請求上下文的操作更加的靈活;
伴隨著請求上下文對象的生成并存儲在棧結構中,Flask還會生成一個“應用上下文”對象,而且“應用上下文”對象也會存儲在另一個棧結構中去。這是兩個版本最大的不同。
我們先看一下0.9版本相關的代碼:
# Flask v0.9 def push(self): """Binds the request context to the current context.""" top = _request_ctx_stack.top if top is not None and top.preserved: top.pop() # Before we push the request context we have to ensure that there # is an application context. app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) _request_ctx_stack.push(self) self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
我們注意到,0.9版本的“請求上下文”的pop方法中,當要將一個“請求上下文”推入_request_ctx_stack棧中的時候,會先檢查另一個棧_app_ctx_stack的棧頂是否存在“應用上下文”對象或者棧頂的“應用上下文”對象的應用是否是當前應用。如果不存在或者不是當前對象,Flask會自動先生成一個“應用上下文”對象,并將其推入_app_ctx_stack中。
我們再看離開上下文時的相關代碼:
# Flask v0.9 def pop(self, exc=None): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """ app_ctx = self._implicit_app_ctx_stack.pop() clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False if exc is None: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) clear_request = True rv = _request_ctx_stack.pop() assert rv is self, "Popped wrong request context. (%r instead of %r)" % (rv, self) # get rid of circular dependencies at the end of the request # so that we don"t require the GC to be active. if clear_request: rv.request.environ["werkzeug.request"] = None # Get rid of the app as well if necessary. if app_ctx is not None: app_ctx.pop(exc)
上面代碼中的細節先不討論。注意到當要離開以上“請求上下文”環境的時候,Flask會先將“請求上下文”對象從_request_ctx_stack棧中銷毀,之后會根據實際的情況確定銷毀“應用上下文”對象。
以下還是以一個簡單的例子進行說明:
# example - Flask v0.9 >>> from flask import Flask, _request_ctx_stack, _app_ctx_stack >>> app = Flask(__name__) # 先檢查兩個棧的內容 >>> _request_ctx_stack._local.__storage__ {} >>> _app_ctx_stack._local.__storage__ {} # 生成一個請求上下文對象 >>> request_context = app.test_request_context() >>> request_context.push() # 請求上下文推入棧后,再次查看兩個棧的內容 >>> _request_ctx_stack._local.__storage__ {應用上下文: {"stack": [ ]}} >>> _app_ctx_stack._local.__storage__ { : {"stack": [ ]}} >>> request_context.pop() # 銷毀請求上下文時,再次查看兩個棧的內容 >>> _request_ctx_stack._local.__storage__ {} >>> _app_ctx_stack._local.__storage__ {}
上部分中簡單介紹了“應用上下文”和“請求上下文”的關系。那什么是“應用上下文”呢?我們先看一下它的類:
class AppContext(object): """The application context binds an application object implicitly to the current thread or greenlet, similar to how the :class:`RequestContext` binds request information. The application context is also implicitly created if a request context is created but the application is not on top of the individual application context. """ def __init__(self, app): self.app = app self.url_adapter = app.create_url_adapter(None) # Like request context, app contexts can be pushed multiple times # but there a basic "refcount" is enough to track them. self._refcnt = 0 def push(self): """Binds the app context to the current context.""" self._refcnt += 1 _app_ctx_stack.push(self) def pop(self, exc=None): """Pops the app context.""" self._refcnt -= 1 if self._refcnt <= 0: if exc is None: exc = sys.exc_info()[1] self.app.do_teardown_appcontext(exc) rv = _app_ctx_stack.pop() assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self) def __enter__(self): self.push() return self def __exit__(self, exc_type, exc_value, tb): self.pop(exc_value)
由以上代碼可以看出:“應用上下文”也是一個上下文對象,可以使用with語句構造一個上下文環境,它也實現了push、pop等方法。“應用上下文”的構造函數也和“請求上下文”類似,都有app、url_adapter等屬性。“應用上下文”存在的一個主要功能就是確定請求所在的應用。
然而,以上的論述卻又讓人產生這樣的疑問:既然“請求上下文”中也包含app等和當前應用相關的信息,那么只要調用_request_ctx_stack.top.app或者魔法current_app就可以確定請求所在的應用了,那為什么還需要“應用上下文”對象呢?對于單應用單請求來說,使用“請求上下文”確實就可以了。然而,Flask的設計理念之一就是多應用的支持。當在一個應用的請求上下文環境中,需要嵌套處理另一個應用的相關操作時,“請求上下文”顯然就不能很好地解決問題了。如何讓請求找到“正確”的應用呢?我們可能會想到,可以再增加一個請求上下文環境,并將其推入_request_ctx_stack棧中。由于兩個上下文環境的運行是獨立的,不會相互干擾,所以通過調用_request_ctx_stack.top.app或者魔法current_app也可以獲得當前上下文環境正在處理哪個應用。這種辦法在一定程度上可行,但是如果對于第二個應用的處理不涉及到相關請求,那也就無從談起“請求上下文”。
為了應對這個問題,Flask中將應用相關的信息多帶帶拿出來,形成一個“應用上下文”對象。這個對象可以和“請求上下文”一起使用,也可以多帶帶拿出來使用。不過有一點需要注意的是:在創建“請求上下文”時一定要創建一個“應用上下文”對象。有了“應用上下文”對象,便可以很容易地確定當前處理哪個應用,這就是魔法current_app。在0.1版本中,current_app是對_request_ctx_stack.top.app的引用,而在0.9版本中current_app是對_app_ctx_stack.top.app的引用。
下面以一個多應用的例子進行說明:
# example - Flask v0.9 >>> from flask import Flask, _request_ctx_stack, _app_ctx_stack # 創建兩個Flask應用 >>> app = Flask(__name__) >>> app2 = Flask(__name__) # 先查看兩個棧中的內容 >>> _request_ctx_stack._local.__storage__ {} >>> _app_ctx_stack._local.__storage__ {} # 構建一個app的請求上下文環境,在這個環境中運行app2的相關操作 >>> with app.test_request_context(): print "Enter app"s Request Context:" print _request_ctx_stack._local.__storage__ print _app_ctx_stack._local.__storage__ print with app2.app_context(): print "Enter app2"s App Context:" print _request_ctx_stack._local.__storage__ print _app_ctx_stack._local.__storage__ print # do something print "Exit app2"s App Context:" print _request_ctx_stack._local.__storage__ print _app_ctx_stack._local.__storage__ print # Result Enter app"s Request Context: {: {"stack": [ ]}} { : {"stack": [ ]}} Enter app2"s App Context: { : {"stack": [ ]}} { : {"stack": [ , ]}} Exit app2"s App Context { : {"stack": [ ]}} { : {"stack": [ ]}}
在以上的例子中:
我們首先創建了兩個Flask應用app和app2;
接著我們構建了一個app的請求上下文環境。當進入這個環境中時,這時查看兩個棧的內容,發現兩個棧中已經有了當前請求的請求上下文對象和應用上下文對象。并且棧頂的元素都是app的請求上下文和應用上下文;
之后,我們再在這個環境中嵌套app2的應用上下文。當進入app2的應用上下文環境時,兩個上下文環境便隔離開來,此時再查看兩個棧的內容,發現_app_ctx_stack中推入了app2的應用上下文對象,并且棧頂指向它。這時在app2的應用上下文環境中,current_app便會一直指向app2;
當離開app2的應用上下文環境,_app_ctx_stack棧便會銷毀app2的應用上下文對象。這時查看兩個棧的內容,發現兩個棧中只有app的請求的請求上下文對象和應用上下文對象。
最后,離開app的請求上下文環境后,兩個棧便會銷毀app的請求的請求上下文對象和應用上下文對象,棧為空。
與上下文對象有關的“全局變量”在Flask中,為了更加方便地處理一些變量,特地提出了“全局變量”的概念。這些全局變量有:
# Flask v0.9 _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_object, "request")) session = LocalProxy(partial(_lookup_object, "session")) g = LocalProxy(partial(_lookup_object, "g")) # 輔助函數 def _lookup_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError("working outside of request context") return getattr(top, name) def _find_app(): top = _app_ctx_stack.top if top is None: raise RuntimeError("working outside of application context") return top.app
可以看出,Flask中使用的一些“全局變量”,包括current_app、request、session、g等都來自于上下文對象。其中current_app一直指向_app_ctx_stack棧頂的“應用上下文”對象,是對當前應用的引用。而request、session、g等一直指向_request_ctx_stack棧頂的“請求上下文”對象,分別引用請求上下文的request、session和g。不過,從 Flask 0.10 起,對象 g 存儲在應用上下文中而不再是請求上下文中。
另外一個問題,在形成這些“全局變量”的時候,使用了werkzeug.local模塊的LocalProxy類。之所以要用該類,主要是為了動態地實現對棧頂元素的引用。如果不使用這個類,在生成上述“全局變量”的時候,它們因為指向棧頂元素,而棧頂元素此時為None,所以這些變量也會被設置為None常量。后續即使有上下文對象被推入棧中,相應的“全局變量”也不會發生改變。為了動態地實現對棧頂元素的引用,這里必須使用werkzeug.local模塊的LocalProxy類。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/38604.html
摘要:本文就主要針對一個應用的運行過程進行簡要分析,后續文章還會對框架的一些具體問題進行分析。所有的請求處理過程,都會在這個上下文對象中進行。和一些全局變量注意當進入這個上下文對象時,會觸發。 相信很多初學Flask的同學(包括我自己),在閱讀官方文檔或者Flask的學習資料時,對于它的認識是從以下的一段代碼開始的: from flask import Flask app = Flask(...
摘要:本篇對應書本第二章程序的基本結構。初始化導入模塊創建類的實例注對于開發者來說,傳給應用程序構造函數的參數是比較容易弄混淆的。不同的請求方法發送到相同的上時,會使用不同的視圖函數進行處理。 本系列筆記是我閱讀Miguel Grinberg的《Flask Web Development》的筆記,標題與書本同步。希望通過記錄技術筆記的方式促進自己對知識的理解。 本篇對應書本第二章:程序的基本...
摘要:的上下文對象有兩種上下文,分別是請求上下文請求的對象,封裝了請求的內容,生命周期請求處理完就結束了根據請求中的,重新載入該訪問者相關的會話信息應用上下文處理請求時用作臨時存儲的對象。 Werkzeugs 是 Flask 的底層WSGI庫。 什么是WSGI? showImg(https://s1.ax1x.com/2018/11/13/iOqdKS.jpg); 一段簡單的app: def...
摘要:前面兩篇講明了怎么支持多線程以及怎么開啟多線程的這篇來講講當后端接收到請求后是怎么一步步封裝的類中的當應用啟動后會通過接收請求中返回的是方法主要做了兩件事情第一件事是通過的另一個方法返回得到了一個封裝好的對象然后調用中的在最后調用了將請求對 前面兩篇講明了flask怎么支持多線程以及怎么開啟多線程的,這篇來講講當后端接收到請求后是怎么一步步封裝的 Flask類中的wsgi_app()當...
摘要:實現一個進程中擁有多個應用上下文機制依賴的數據結構上下文機制的實現基于的。 什么是上下文? flask框架中的上下文本質上就是兩個類,我們可以先看一下他的初始化函數:應用上下文 class AppContext(object): The application context binds an application object implicitly to the c...
閱讀 3522·2023-04-25 14:57
閱讀 2569·2021-11-22 14:56
閱讀 2092·2021-09-29 09:45
閱讀 1775·2021-09-22 15:53
閱讀 3325·2021-08-25 09:41
閱讀 907·2019-08-29 15:22
閱讀 3299·2019-08-29 13:22
閱讀 3130·2019-08-29 13:08