Flask源碼閱讀筆記(四)
flask的全局變數request
在上一個筆記中,提到了request context里有兩個特殊的變數,request和session,在這個筆記中,結合flask源碼,我們來看看在進行開發的時候,是怎麼從上下文信息中取得這兩個變數的。
這裡要提到flask的全局變數,它們都定義在flask/globals.py模塊中,如下:
# context localsn_request_ctx_stack = LocalStack()n_app_ctx_stack = LocalStack()ncurrent_app = LocalProxy(_find_app)nrequest = LocalProxy(partial(_lookup_req_object, request))nsession = LocalProxy(partial(_lookup_req_object, session))ng = LocalProxy(partial(_lookup_app_object, g))n
種類上,request context和app context都是LocalStack類,這個類的初始化代碼如下:
class LocalStack(object): n def __init__(self):n self._local = Local()n
它只有一個成員變數(其實還有一個property,top,用來返回棧頂元素),是個Local類,Local的初始化:
class Local(object):n def __init__(self):n object.__setattr__(self, __storage__, {})n object.__setattr__(self, __ident_func__, get_ident)n
它的實例里有兩個元素,__storage__這個字典是存儲空間,而__ident_func__和多線程有關,後面會涉及,先不討論。
現在我們回過頭來看LocalStack的入棧和出棧操作:
def push(self, obj):n """Pushes a new item to the stack"""n rv = getattr(self._local, stack, None)n if rv is None:n self._local.stack = rv = []n rv.append(obj)n return rvnn def pop(self):n """Removes the topmost item from the stack, will return then old value or `None` if the stack was already empty.n """n stack = getattr(self._local, stack, None)n if stack is None:n return Nonen elif len(stack) == 1:n release_local(self._local)n return stack[-1]n else:n return stack.pop()n
那麼可以看出,LocalStack類把數據存儲在自己成員變數(Local)的中,在Local中綁定了一個stack屬性。再看看Local的方法,發現它裡面把__setattr__和__getattr__重載了。我們都知道,這兩個內建函數是類實例設置和讀取屬性時調用的,它們被重載成了這個樣子:
def __getattr__(self, name):n try:n return self.__storage__[self.__ident_func__()][name]n except KeyError:n raise AttributeError(name)nn def __setattr__(self, name, value):n ident = self.__ident_func__()n storage = self.__storage__n try:n storage[ident][name] = valuen except KeyError:n storage[ident] = {name: value}n
所以,當給Local類添加一個屬性的時候,其實就是在它的成員變數__storage__這個字典里增加一個鍵值對,鍵是indent,值是一個字典,也就是我們添加的屬性名稱和它的值。
另外四個全局變數,它們都是LocalProxy類,這個類又是個啥呢?首先看初始化的代碼:
class LocalProxy(object):n def __init__(self, local, name=None):n object.__setattr__(self, _LocalProxy__local, local)n object.__setattr__(self, __name__, name)n
這裡有兩個成員,其中一個是一個私有成員_local,它存儲了初始化時候的入參local。看到這裡還是一頭霧水,這樣做怎麼能把request從上下文中取出來呢?這個LocalProxy到底是個啥?
再來重新看一眼request的初始化過程:
request = LocalProxy(partial(_lookup_req_object, request))n
結合LocalProxy的初始化函數,那麼這裡request的私有變數_local就是這個偏函數了,這個偏函數的作用如下:
def _lookup_req_object(name):n top = _request_ctx_stack.topnif top is None:nraise RuntimeError(_request_ctx_err_msg)nreturn getattr(top, name)n
其實就是從request context中取出request對象。那麼從全局變數request到request context里的request是如何產生聯繫的?答案就在於LocalProxy也重載了__getattr__和__setattr__,看這裡:
class LocalProxy(object):n……n def __getattr__(self, name):n if name == __members__:n return dir(self._get_current_object())n return getattr(self._get_current_object(), name)nn __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)nn def _get_current_object(self):n """Return the current object. This is useful if you want the realn object behind the proxy at a time for performance reasons or becausen you want to pass the object into a different context.n """n if not hasattr(self.__local, __release_local__):n return self.__local()n try:n return getattr(self.__local, self.__name__)n except AttributeError:n raise RuntimeError(no object bound to %s % self.__name__)n
當試圖從LocalProxy設置或者取出屬性的時候,這個操作會被_get_current_object捕獲,並從私有變數_local中得到context中的對象(這裡也就是request對象),並把需要的屬性操作傳遞給它。如此一來,相當於給request context中的request對象穿了一個馬甲,全局變數request會把操作導向給對應的request。
不得不說,這一段代碼大量重載了內建函數,而且真有點繞,看了好久又結合了網上一些大神的文檔才了解了個大概。
推薦閱讀: