1. 程式人生 > >flask中current_app、g、request、session原始碼的深究和理解

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

本文是我在學習flask中對上下文和幾個類似全域性變數的思考和研究,也有我自己的理解在內。

為了研究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原理上是一樣的,所以將其歸為一類,稱為請求上下文。

我們從最裡面看起,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方法,我繼續找到原始碼:
    

    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(),對應另一個類,繼續看原始碼:
    

    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函式,檢視原始碼:
    

    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()來進入應用上下文。