Flask上下文源碼分析(一)

flask中的上下文分兩種,application context和request context,即應用上下文和請求上下文。
 
從名字上看,可能會有誤解,認爲應用上下文是一個應用的全局變量,全部請求均可以訪問修改其中的內容;而請求上下文則是請求內可訪問的內容。
但事實上,這二者並非全局與局部的關係,它們都處於一個請求的局部中。
 
先說結論每一個請求的g都是獨立的,而且在整個請求內都是可訪問修改的。
 
下面來研究一下。
 
上下文類的定義:
 
上下文類定義在flask.ctx模塊中
 
class AppContext(object):
    def __init__(self, app):
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()

        # Like request context, app contexts can be pushed multiple times
        # but there a basic "refcount" is enough to track them.
        self._refcnt = 0
 
查看了源代碼,AppContext類便是應用上下文,能夠看到裏面只保存了幾個變量,其中比較重要的有:
app是當前web應用對象的引用,如Flask;還有g,用來保存須要在每一個請求中須要用到的請求內全局變量。
 
 
class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []
 
RequestContext即請求上下文,其中有咱們熟悉的request和session,app和應用上下文中的app含義相同。
 
上下文對象的做用域
 
那麼這兩種上下文運行時是怎麼被使用的呢?
 
線程有個叫作ThreadLocal的類,也就是一般實現線程隔離的類。而werkzeug本身實現了它的線程隔離類:werkzeug.local.Local。LocalStack就是用Local實現的。
 
在flask.globals模塊中定義了兩個LocalStack對象:
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
 
LocalStack是flask定義的線程隔離的棧存儲對象,分別用來保存應用和請求上下文。
它是線程隔離的意思就是說,對於不一樣的線程,它們訪問這兩個對象看到的結果是不同的、徹底隔離的。這是根據pid的不一樣實現的,相似於門牌號。
 
而每一個傳給flask對象的請求,都是在不一樣的線程中處理,並且同一時刻每一個線程只處理一個請求。因此對於每一個請求來講,它們徹底不用擔憂本身上下文中的數據被別的請求所修改。
 
而後就能夠解釋這個特性:從flask模塊中引入的g、session、request、current_app是怎麼作到同一個對象能在全部請求中使用而且不會衝突。
 
這幾個對象仍是定義在flask.globals中:
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
 
LocalProxy類的構造函數接收一個callable參數,上面這幾個就傳入了一個偏函數。以g爲例,當對g進行操做時,就會調用做爲參數的偏函數,並把操做轉發到偏函數返回的對象上。
 
查看這幾個函數的實現:
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application 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
 
因爲_app_ctx_stack和_request_ctx_stack都是線程隔離的,因此對g的調用就是這樣一個過程:
 
訪問g-->從當前線程的應用上下文棧頂獲取應用上下文-->取出其中的g對象-->進行操做。
因此能夠經過一個g對象而讓全部線程互不干擾的訪問本身的g。
 
上下文對象的推送
 
構建Flask對象後並不會推送上下文,而在Flask對象調用run()做爲WSGI 應用啓動後,每當有請求進入時,在推送請求上下文前,若是有必要就會推送應用上下文。但運行了run就會阻塞程序,因此在shell中調試時,必須手動推送上下文;或者使用flask-scripts,它運行的任務會在開始時自動推送。
 
上面加粗的「若是有必要」,那麼什麼叫有必要呢?是否是意味着在每一個線程裏應用上下文只會被推送一次、一次請求結束下一次請求來的時候就不用再推送應用上下文了呢?
來看RequestContext的源碼,push函數:
def push(self):    
    top = _request_ctx_stack.top
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)

    # 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)
 
flask在推送請求上下文的時候調用push函數,他會檢查當前線程的應用上下文棧頂是否有應用上下文;若是有,判斷與請求上下文是否屬於同一個應用。在單WSGI應用的程序中,後者的判斷無心義。
此時,只要沒有應用上下文就會推送一個當前應用的上下文,而且把該上下文記錄下來。
 
請求處理結束,調用auto_pop函數,其中又調用自身的pop函數:
def pop(self, exc=None): 
   app_ctx = self._implicit_app_ctx_stack.pop()
         ………………      
      ………………
   rv = _request_ctx_stack.pop()   
    # Get rid of the app as well if necessary.
    if app_ctx is not None:
        app_ctx.pop(exc)
 
會把請求上下文和應用上下文都pop掉。
 
故,在單WSGI應用環境下,每一個請求的兩個上下文都是徹底獨立的(獨立於線程上曾經的請求,獨立於其餘線程的請求)。Q.E.D
 
那麼,何時不必推送呢?事實上,每次請求到來的時候都會推送,都是有必要的。由於當Flask在做爲WSGI應用運行的時候,不可能出現當前線程的應用上下文已存在的狀況。
 
那麼就要搞清何時會有已存在的應用上下文。
 
該博文在最後提到了「兩個疑問」:①應用和請求上下文在運行時都是線程隔離的,爲什麼要分開來?②每一個線程同時只處理一個請求,上下文棧確定只有一個對象,爲什麼要用棧來存儲?
博主認爲,這兩個設計都是爲了在離線狀態下調試用:
 
 
因此,綜上所述,在非離線狀態下,上下文棧在每一個WSGI應用裏是獨立的,而每一個應用裏線程同時只處理一個請求,故上下文棧確定只有一個對象。而且,在請求結束後都會釋放,因此新的請求來的時候都會從新推送兩個上下文。
 
小結:
解釋了這麼多,對於flask編程來講,只有一個應用上的結論:每一個請求的g都是獨立的,而且在整個請求內都是可訪問修改的。
 
ps.原本只是想知道可否在請求中保存一個變量,就研究了g的生存週期和做用範圍,最後花了5個小時左右讀了flask英文文檔、各類博文和源代碼,寫了這些文字。我這對於細枝末節的東西吹毛求疵的精神真是害苦了我。。。
 
附上一些其餘的筆記:
 
全局變量g,會在每次請求到來時重置
flask. g

Just store on this whatever you want. For example a database connection or the user that is currently logged in.web

 
Starting with Flask 0.10 this is stored on the application context and no longer on the request context which means it becomes available if only the application context is bound and not yet a request
這個意思是g在應用上下文,而不是請求上下文。只要push了應用上下文就可使用g對象
不要誤解爲g是整個程序內共享的
 
 
The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests.
 
推送程序上下文:app = Flask(xxx),     app.app_context().push() 推送了程序上下文,g可使用,當前線程的current_app指向app
相關文章
相關標籤/搜索