flask中current_app、g、request、session源碼的深究和理解

本文是我在學習flask中對上下文和幾個相似全局變量的思考和研究,也有我本身的理解在內。python

爲了研究flask中的current_app、g、request、session,我找到定義在global.py的源碼:編程

# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
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'))

能夠看到主要由_lookup_req_object、_lookup_app_object、_find_app等組成,我先來分析request和session
其實request和session原理上是同樣的,因此將其歸爲一類,稱爲請求上下文。flask

咱們從最裏面看起,partial(_lookup_req_object, 'request'),最外層是一個偏函數,不過這不是重點,它主要是將'request'傳給_lookup_req_object,沒有其餘含義, 順着_lookup_req_object找到它的源碼服務器

def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
    raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)

從最後的return能夠看到,這個函數的主要功能是從top中取出鍵值爲'request'的內容,top是一個字典,top從_request_ctx_stack.top中來,在上面的源碼中 _request_ctx_stack = LocalStack(),從名字來看LocalStack應該是一個棧類,應該有pop,push,top方法,我繼續找到源碼:cookie

def __init__(self):
    self._local = Local()
...
...
def push(self, obj):
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
        self._local.stack = rv = []
    rv.append(obj)
    return rv

def pop(self):
    """Removes the topmost item from the stack, will return the
    old value or `None` if the stack was already empty.
    """
    stack = getattr(self._local, 'stack', None)
    if stack is None:
        return None
    elif len(stack) == 1:
        release_local(self._local)
        return stack[-1]
    else:
        return stack.pop()

@property
def top(self):
    """The topmost item on the stack.  If the stack is empty,
    `None` is returned.
    """
    try:
        return self._local.stack[-1]
    except (AttributeError, IndexError):
        return None

能夠看到LocalStack()這個類有一個屬性self._local = Local(),對應另外一個類,繼續看源碼:session

def __init__(self):
    object.__setattr__(self, '__storage__', {})
    object.__setattr__(self, '__ident_func__', get_ident)
...
...
def __getattr__(self, name):
    try:
        return self.__storage__[self.__ident_func__()][name]
    except KeyError:
        raise AttributeError(name)

def __setattr__(self, name, value):
    ident = self.__ident_func__()
    storage = self.__storage__
    try:
        storage[ident][name] = value
    except KeyError:
        storage[ident] = {name: value}

我截取了幾個重要的函數,LocalStack()中的push,用到了Local()中的__setattr__();pop用到了__getattr__(),看到push和pop都是對'stack'這個鍵值進行查詢和賦值,咱們轉到Local()這個類中,這個類有兩個實例屬性,__storage__和__ident_func__,前者是一個字典,後者是一個函數,咱們看一下這個get_ident函數,查看源碼:app

def get_ident(): # real signature unknown; restored from __doc__
    """
    get_ident() -> integer

    Return a non-zero integer that uniquely identifies the current thread
    amongst other threads that exist simultaneously.
    This may be used to identify per-thread resources.
    Even though on some platforms threads identities may appear to be
    allocated consecutive numbers starting at 1, this behavior should not
    be relied upon, and the number should be seen purely as a magic cookie.
    A thread's identity may be reused for another thread after it exits.
    """
    return 0

顯然這個函數不是python寫的,由於它來自_thread.py,是一個底層庫,從名字能夠猜到和線程有關,根據描述,它返回一個非零整數,表明了當前線程id,咱們再看看__setattr__這個方法,它實際上是一個字典嵌套列表再嵌套字典的數據,__storage__是一個字典,它裏面的鍵值被賦值爲當前線程id,這個鍵值對應的值是另外一個字典:{'stack':['request':r_val,'session':s_val]},這樣和前面聯繫起來就很好理解了,Local()中的__storage__存儲的格式爲{thread_id:{'stack':['request':r_val,'session':s_val]}},LocalStack()中的top方法,返回了'stack'中最後一個加入的元素,也就是最新的元素,我本身理解爲服務器接受的最新的請求,在框架外看起來request和session是一個全局變量,其實內部已經由進程id將其分隔開了,即便同時有多個請求過來,進程間的數據也不會混亂。框架

同理current_app和g也同樣,惟一不一樣的是,current_app、g和request、session是兩個不一樣的實例,注意前面 _request_ctx_stack = LocalStack()、_app_ctx_stack = LocalStack(),因此'stack'中存的數據也不同,current_app和g稱爲應用上下文,二者仍是有區別的。
LocalProxy 則是一個典型的代理模式實現,它在構造時接受一個 callable 的參數(好比一個函數),這個參數被調用後的返回值自己應該是一個 Thread Local 對象。對一個 LocalProxy 對象的全部操做,包括屬性訪問、方法調用(固然方法調用就是屬性訪問)甚至是二元操做,都會轉發到那個 callable 參數返回的 Thread Local 對象上。
LocalProxy 的一個使用場景是 LocalStack 的 call 方法。好比 my_local_stack 是一個 LocalStack 實例,那麼 my_local_stack() 能返回一個 LocalProxy 對象,這個對象始終指向 my_local_stack 的棧頂元素。若是棧頂元素不存在,訪問這個 LocalProxy 的時候會拋出 RuntimeError。
須要注意的是,若是須要離線編程,尤爲在寫測試代碼時,須要將應用上下文push到棧中去,否則current_app會指向空的_app_ctx_stack棧頂,天然也就沒法工做了。
咱們能夠經過current_app的值來判斷是否進入應用上下文中,能夠用app.app_context().push()來進入應用上下文。ide

相關文章
相關標籤/搜索