免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Flask源碼解析:上下文

原文:http://cizixs.com/2017/01/13/flask-insight-context

全文約 16600 字,讀完可能需要 25 分鐘。

這是 flask 源碼解析系列文章的其中一篇,本系列已發(fā)布文章列表:

上下文(application context 和 request context)

上下文一直是計(jì)算機(jī)中難理解的概念,在 知乎的一個(gè)問題 下面有個(gè)很通俗易懂的回答:

每一段程序都有很多外部變量。只有像 Add 這種簡單的函數(shù)才是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨(dú)立運(yùn)行。你為了使他們運(yùn)行,就要給所有的外部變量一個(gè)一個(gè)寫一些值進(jìn)去。這些值的集合就叫上下文。

  • vzch

比如,在 flask 中,視圖函數(shù)需要知道它執(zhí)行情況的請求信息(請求的 url,參數(shù),方法等)以及應(yīng)用信息(應(yīng)用中初始化的數(shù)據(jù)庫等),才能夠正確運(yùn)行。

最直觀地做法是把這些信息封裝成一個(gè)對象,作為參數(shù)傳遞給視圖函數(shù)。但是這樣的話,所有的視圖函數(shù)都需要添加對應(yīng)的參數(shù),即使該函數(shù)內(nèi)部并沒有使用到它。

flask 的做法是把這些信息作為類似全局變量的東西,視圖函數(shù)需要的時(shí)候,可以使用 from flaskimport request 獲取。但是這些對象和全局變量不同的是----它們必須是動(dòng)態(tài)的,因?yàn)樵诙嗑€程或者多協(xié)程的情況下,每個(gè)線程或者協(xié)程獲取的都是自己獨(dú)特的對象,不會(huì)互相干擾。

那么如何實(shí)現(xiàn)這種效果呢?如果對 python 多線程比較熟悉的話,應(yīng)該知道多線程中有個(gè)非常類似的概念 threading.local,可以實(shí)現(xiàn)多線程訪問某個(gè)變量的時(shí)候只看到自己的數(shù)據(jù)。內(nèi)部的原理說起來也很簡單,這個(gè)對象有一個(gè)字典,保存了線程 id 對應(yīng)的數(shù)據(jù),讀取該對象的時(shí)候,它動(dòng)態(tài)地查詢當(dāng)前線程 id 對應(yīng)的數(shù)據(jù)。flaskpython 上下文的實(shí)現(xiàn)也類似,后面會(huì)詳細(xì)解釋。

flask 中有兩種上下文: application context 和 request context。上下文有關(guān)的內(nèi)容定義在 globals.py 文件,文件的內(nèi)容也非常短:

  1. def _lookup_req_object(name):

  2.    top = _request_ctx_stack.top

  3.    if top is None:

  4.        raise RuntimeError(_request_ctx_err_msg)

  5.    return getattr(top, name)

  6. def _lookup_app_object(name):

  7.    top = _app_ctx_stack.top

  8.    if top is None:

  9.        raise RuntimeError(_app_ctx_err_msg)

  10.    return getattr(top, name)

  11. def _find_app():

  12.    top = _app_ctx_stack.top

  13.    if top is None:

  14.        raise RuntimeError(_app_ctx_err_msg)

  15.    return top.app

  16. # context locals

  17. _request_ctx_stack = LocalStack()

  18. _app_ctx_stack = LocalStack()

  19. current_app = LocalProxy(_find_app)

  20. request = LocalProxy(partial(_lookup_req_object, 'request'))

  21. session = LocalProxy(partial(_lookup_req_object, 'session'))

  22. g = LocalProxy(partial(_lookup_app_object, 'g'))

flask 提供兩種上下文: application context 和 request context 。 app lication context 又演化出來兩個(gè)變量 current_app 和 g,而 request context 則演化出來 request 和 session。

這里的實(shí)現(xiàn)用到了兩個(gè)東西: LocalStack 和 LocalProxy。它們兩個(gè)的結(jié)果就是我們可以動(dòng)態(tài)地獲取兩個(gè)上下文的內(nèi)容,在并發(fā)程序中每個(gè)視圖函數(shù)都會(huì)看到屬于自己的上下文,而不會(huì)出現(xiàn)混亂。

LocalStack 和 LocalProxy 都是 werkzeug 提供的,定義在 local.py 文件中。在分析這兩個(gè)類之前,我們先介紹這個(gè)文件另外一個(gè)基礎(chǔ)的類 Local。 Local 就是實(shí)現(xiàn)了類似 threading.local的效果 ----多線程或者多協(xié)程情況下全局變量的隔離效果。下面是它的代碼:

  1. # since each thread has its own greenlet we can just use those as identifiers

  2. # for the context.  If greenlets are not available we fall back to the

  3. # current thread ident depending on where it is.

  4. try:

  5.    from greenlet import getcurrent as get_ident

  6. except ImportError:

  7.    try:

  8.        from thread import get_ident

  9.    except ImportError:

  10.        from _thread import get_ident

  11. class Local(object):

  12.    __slots__ = ('__storage__', '__ident_func__')

  13.    def __init__(self):

  14.        # 數(shù)據(jù)保存在 __storage__ 中,后續(xù)訪問都是對該屬性的操作

  15.        object.__setattr__(self, '__storage__', {})

  16.        object.__setattr__(self, '__ident_func__', get_ident)

  17.    def __call__(self, proxy):

  18.        '''Create a proxy for a name.'''

  19.        return LocalProxy(self, proxy)

  20.    # 清空當(dāng)前線程/協(xié)程保存的所有數(shù)據(jù)

  21.    def __release_local__(self):

  22.        self.__storage__.pop(self.__ident_func__(), None)

  23.    # 下面三個(gè)方法實(shí)現(xiàn)了屬性的訪問、設(shè)置和刪除。

  24.    # 注意到,內(nèi)部都調(diào)用 `self.__ident_func__` 獲取當(dāng)前線程或者協(xié)程的 id,然后再訪問對應(yīng)的內(nèi)部字典。

  25.    # 如果訪問或者刪除的屬性不存在,會(huì)拋出 AttributeError。

  26.    # 這樣,外部用戶看到的就是它在訪問實(shí)例的屬性,完全不知道字典或者多線程/協(xié)程切換的實(shí)現(xiàn)

  27.    def __getattr__(self, name):

  28.        try:

  29.            return self.__storage__[self.__ident_func__()][name]

  30.        except KeyError:

  31.            raise AttributeError(name)

  32.    def __setattr__(self, name, value):

  33.        ident = self.__ident_func__()

  34.        storage = self.__storage__

  35.        try:

  36.            storage[ident][name] = value

  37.        except KeyError:

  38.            storage[ident] = {name: value}

  39.    def __delattr__(self, name):

  40.        try:

  41.            del self.__storage__[self.__ident_func__()][name]

  42.        except KeyError:

  43.            raise AttributeError(name)

可以看到, Local 對象內(nèi)部的數(shù)據(jù)都是保存在 __storage__ 屬性的,這個(gè)屬性變量是個(gè)嵌套的字典: map[ident]map[key]value。最外面字典 key 是線程或者協(xié)程的 identity,value 是另外一個(gè)字典,這個(gè)內(nèi)部字典就是用戶自定義的 key-value 鍵值對。用戶訪問實(shí)例的屬性,就變成了訪問內(nèi)部的字典,外面字典的 key 是自動(dòng)關(guān)聯(lián)的。 __ident_func 是 協(xié)程的 get_current 或者線程的 get_ident,從而獲取當(dāng)前代碼所在線程或者協(xié)程的 id。

除了這些基本操作之外, Local 還實(shí)現(xiàn)了 __release_local__,用來清空(析構(gòu))當(dāng)前線程或者協(xié)程的數(shù)據(jù)(狀態(tài))。 __call__ 操作來創(chuàng)建一個(gè) LocalProxy 對象, LocalProxy 會(huì)在下面講到。

理解了 Local,我們繼續(xù)回來看另外兩個(gè)類。

LocalStack 是基于 Local 實(shí)現(xiàn)的棧結(jié)構(gòu)。如果說 Local 提供了多線程或者多協(xié)程隔離的屬性訪問,那么 LocalStack 就提供了隔離的棧訪問。下面是它的實(shí)現(xiàn)代碼,可以看到它提供了 push、 pop 和 top 方法。

__release_local__ 可以用來清空當(dāng)前線程或者協(xié)程的棧數(shù)據(jù), __call__ 方法返回當(dāng)前線程或者協(xié)程棧頂元素的代理對象。

  1. class LocalStack(object):

  2.    '''This class works similar to a :class:`Local` but keeps a stack

  3.    of objects instead. '''

  4.    def __init__(self):

  5.        self._local = Local()

  6.    def __release_local__(self):

  7.        self._local.__release_local__()

  8.    def __call__(self):

  9.        def _lookup():

  10.            rv = self.top

  11.            if rv is None:

  12.                raise RuntimeError('object unbound')

  13.            return rv

  14.        return LocalProxy(_lookup)

  15.    # push、pop 和 top 三個(gè)方法實(shí)現(xiàn)了棧的操作,

  16.    # 可以看到棧的數(shù)據(jù)是保存在 self._local.stack 屬性中的

  17.    def push(self, obj):

  18.        '''Pushes a new item to the stack'''

  19.        rv = getattr(self._local, 'stack', None)

  20.        if rv is None:

  21.            self._local.stack = rv = []

  22.        rv.append(obj)

  23.        return rv

  24.    def pop(self):

  25.        '''Removes the topmost item from the stack, will return the

  26.        old value or `None` if the stack was already empty.

  27.        '''

  28.        stack = getattr(self._local, 'stack', None)

  29.        if stack is None:

  30.            return None

  31.        elif len(stack) == 1:

  32.            release_local(self._local)

  33.            return stack[-1]

  34.        else:

  35.            return stack.pop()

  36.    @property

  37.    def top(self):

  38.        '''The topmost item on the stack.  If the stack is empty,

  39.        `None` is returned.

  40.        '''

  41.        try:

  42.            return self._local.stack[-1]

  43.        except (AttributeError, IndexError):

  44.            return None

我們在之前看到了 request context 的定義,它就是一個(gè) LocalStack 的實(shí)例:

  1. _request_ctx_stack = LocalStack()

它會(huì)當(dāng)前線程或者協(xié)程的請求都保存在棧里,等使用的時(shí)候再從里面讀取。至于為什么要用到棧結(jié)構(gòu),而不是直接使用 Local,我們會(huì)在后面揭曉答案,你可以先思考一下。

LocalProxy 是一個(gè) Local 對象的代理,負(fù)責(zé)把所有對自己的操作轉(zhuǎn)發(fā)給內(nèi)部的 Local 對象。 LocalProxy 的構(gòu)造函數(shù)介紹一個(gè) callable 的參數(shù),這個(gè) callable 調(diào)用之后需要返回一個(gè) Local實(shí)例,后續(xù)所有的屬性操作都會(huì)轉(zhuǎn)發(fā)給 callable 返回的對象。

  1. class LocalProxy(object):

  2.    '''Acts as a proxy for a werkzeug local.

  3.    Forwards all operations to a proxied object. '''

  4.    __slots__ = ('__local', '__dict__', '__name__')

  5.    def __init__(self, local, name=None):

  6.        object.__setattr__(self, '_LocalProxy__local', local)

  7.        object.__setattr__(self, '__name__', name)

  8.    def _get_current_object(self):

  9.        '''Return the current object.'''

  10.        if not hasattr(self.__local, '__release_local__'):

  11.            return self.__local()

  12.        try:

  13.            return getattr(self.__local, self.__name__)

  14.        except AttributeError:

  15.            raise RuntimeError('no object bound to %s' % self.__name__)

  16.    @property

  17.    def __dict__(self):

  18.        try:

  19.            return self._get_current_object().__dict__

  20.        except RuntimeError:

  21.            raise AttributeError('__dict__')

  22.    def __getattr__(self, name):

  23.        if name == '__members__':

  24.            return dir(self._get_current_object())

  25.        return getattr(self._get_current_object(), name)

  26.    def __setitem__(self, key, value):

  27.        self._get_current_object()[key] = value

這里實(shí)現(xiàn)的關(guān)鍵是把通過參數(shù)傳遞進(jìn)來的 Local 實(shí)例保存在 __local 屬性中,并定義了 _get_current_object() 方法獲取當(dāng)前線程或者協(xié)程對應(yīng)的對象。

NOTE:前面雙下劃線的屬性,會(huì)保存到 _ClassName__variable 中。所以這里通過 '_LocalProxy__local' 設(shè)置的值,后面可以通過 self.__local 來獲取。關(guān)于這個(gè)知識(shí)點(diǎn),可以查看 stackoverflow 的這個(gè)問題。

然后 LocalProxy 重寫了所有的魔術(shù)方法(名字前后有兩個(gè)下劃線的方法),具體操作都是轉(zhuǎn)發(fā)給代理對象的。這里只給出了幾個(gè)魔術(shù)方法,感興趣的可以查看源碼中所有的魔術(shù)方法。

繼續(xù)回到 request context 的實(shí)現(xiàn):

  1. _request_ctx_stack = LocalStack()

  2. request = LocalProxy(partial(_lookup_req_object, 'request'))

  3. session = LocalProxy(partial(_lookup_req_object, 'session'))

再次看這段代碼希望能看明白, _request_ctx_stack 是多線程或者協(xié)程隔離的棧結(jié)構(gòu), request每次都會(huì)調(diào)用 _lookup_req_object 棧頭部的數(shù)據(jù)來獲取保存在里面的 requst context。

那么請求上下文信息是什么被放在 stack 中呢?還記得之前介紹的 wsgi_app() 方法有下面兩行代碼嗎?

  1. ctx = self.request_context(environ)

  2. ctx.push()

每次在調(diào)用 app.__call__ 的時(shí)候,都會(huì)把對應(yīng)的請求信息壓棧,最后執(zhí)行完請求的處理之后把它出棧。

我們來看看 request_context, 這個(gè) 方法只有一行代碼:

  1. def request_context(self, environ):

  2.    return RequestContext(self, environ)

它調(diào)用了 RequestContext,并把 self 和請求信息的字典 environ 當(dāng)做參數(shù)傳遞進(jìn)去。追蹤到 RequestContext 定義的地方,它出現(xiàn)在 ctx.py 文件中,代碼如下:

  1. class RequestContext(object):

  2.    '''The request context contains all request relevant information.  It is

  3.    created at the beginning of the request and pushed to the

  4.    `_request_ctx_stack` and removed at the end of it.  It will create the

  5.    URL adapter and request object for the WSGI environment provided.

  6.    '''

  7.    def __init__(self, app, environ, request=None):

  8.        self.app = app

  9.        if request is None:

  10.            request = app.request_class(environ)

  11.        self.request = request

  12.        self.url_adapter = app.create_url_adapter(self.request)

  13.        self.match_request()

  14.    def match_request(self):

  15.        '''Can be overridden by a subclass to hook into the matching

  16.        of the request.

  17.        '''

  18.        try:

  19.            url_rule, self.request.view_args = \

  20.                self.url_adapter.match(return_rule=True)

  21.            self.request.url_rule = url_rule

  22.        except HTTPException as e:

  23.            self.request.routing_exception = e

  24.    def push(self):

  25.        '''Binds the request context to the current context.'''

  26.        # Before we push the request context we have to ensure that there

  27.        # is an application context.

  28.        app_ctx = _app_ctx_stack.top

  29.        if app_ctx is None or app_ctx.app != self.app:

  30.            app_ctx = self.app.app_context()

  31.            app_ctx.push()

  32.            self._implicit_app_ctx_stack.append(app_ctx)

  33.        else:

  34.            self._implicit_app_ctx_stack.append(None)

  35.        _request_ctx_stack.push(self)

  36.        self.session = self.app.open_session(self.request)

  37.        if self.session is None:

  38.            self.session = self.app.make_null_session()

  39.    def pop(self, exc=_sentinel):

  40.        '''Pops the request context and unbinds it by doing that.  This will

  41.        also trigger the execution of functions registered by the

  42.        :meth:`~flask.Flask.teardown_request` decorator.

  43.        '''

  44.        app_ctx = self._implicit_app_ctx_stack.pop()

  45.        try:

  46.            clear_request = False

  47.            if not self._implicit_app_ctx_stack:

  48.                self.app.do_teardown_request(exc)

  49.                request_close = getattr(self.request, 'close', None)

  50.                if request_close is not None:

  51.                    request_close()

  52.                clear_request = True

  53.        finally:

  54.            rv = _request_ctx_stack.pop()

  55.            # get rid of circular dependencies at the end of the request

  56.            # so that we don't require the GC to be active.

  57.            if clear_request:

  58.                rv.request.environ['werkzeug.request'] = None

  59.            # Get rid of the app as well if necessary.

  60.            if app_ctx is not None:

  61.                app_ctx.pop(exc)

  62.    def auto_pop(self, exc):

  63.        if self.request.environ.get('flask._preserve_context') or \

  64.           (exc is not None and self.app.preserve_context_on_exception):

  65.            self.preserved = True

  66.            self._preserved_exc = exc

  67.        else:

  68.            self.pop(exc)

  69.    def __enter__(self):

  70.        self.push()

  71.        return self

  72.    def __exit__(self, exc_type, exc_value, tb):

  73.        self.auto_pop(exc_value)

每個(gè) request context 都保存了當(dāng)前請求的信息,比如 request 對象和 app 對象。在初始化的最后,還調(diào)用了 match_request 實(shí)現(xiàn)了 路由的匹配邏輯

push 操作就是把該請求的 ApplicationContext(如果 _app_ctx_stack 棧頂不是當(dāng)前請求所在 app,需要?jiǎng)?chuàng)建新的 app context) 和 RequestContext 有關(guān)的信息保存到對應(yīng)的棧上,壓棧后還會(huì)保存 session 的信息; pop 則相反,把 request context 和 application context 出棧,做一些清理性的工作。

到這里,上下文的實(shí)現(xiàn)就比較清晰了:每次有請求過來的時(shí)候,flask 會(huì)先創(chuàng)建當(dāng)前線程或者進(jìn)程需要處理的兩個(gè)重要上下文對象,把它們保存到隔離的棧里面,這樣視圖函數(shù)進(jìn)行處理的時(shí)候就能直接從棧上獲取這些信息。

NOTE:因?yàn)?app 實(shí)例只有一個(gè),因此多個(gè) request 共享了 application context

到這里,關(guān)于 context 的實(shí)現(xiàn)和功能已經(jīng)講解得差不多了。還有兩個(gè)疑惑沒有解答。

  1. 為什么要把 request context 和 application context 分開?每個(gè)請求不是都同時(shí)擁有這兩個(gè)上下文信息嗎?

  2. 為什么 request context 和 application context 都有實(shí)現(xiàn)成棧的結(jié)構(gòu)?每個(gè)請求難道會(huì)出現(xiàn)多個(gè) request context 或者 application context 嗎?

第一個(gè)答案是'靈活度',第二個(gè)答案是' 多application'。雖然在實(shí)際運(yùn)行中,每個(gè)請求對應(yīng)一個(gè) request context 和一個(gè) application context,但是在測試或者 python shell 中運(yùn)行的時(shí)候,用戶可以單獨(dú)創(chuàng)建 request context 或者 application context,這種靈活度方便用戶的不同的使用場景;而且棧可以讓 redirect 更容易實(shí)現(xiàn),一個(gè)處理函數(shù)可以從棧中獲取重定向路徑的多個(gè)請求信息。application 設(shè)計(jì)成棧也是類似,測試的時(shí)候可以添加多個(gè)上下文,另外一個(gè)原因是 flask 可以多個(gè) application  同時(shí)運(yùn)行:

  1. from werkzeug.wsgi import DispatcherMiddleware

  2. from frontend_app import application as frontend

  3. from backend_app import application as backend

  4. application = DispatcherMiddleware(frontend, {

  5.    '/backend':     backend

  6. })

這個(gè)例子就是使用 werkzeug 的 DispatcherMiddleware 實(shí)現(xiàn)多個(gè) app 的分發(fā),這種情況下 _app_ctx_stack 棧里會(huì)出現(xiàn)兩個(gè) application context。

Update: 為什么要用 LocalProxy

寫完這篇文章之后,收到有位讀者的疑問:為什么要使用 LocalProxy?不使用 LocalProxy 直接訪問 LocalStack 的對象會(huì)有什么問題嗎?

這是個(gè)很好的問題,上面也確實(shí)沒有很明確地給出這個(gè)答案。這里解釋一下!

首先明確一點(diǎn), Local 和 LocalStack 實(shí)現(xiàn)了不同線程/協(xié)程之間的數(shù)據(jù)隔離。在為什么用 LocalStack 而不是直接使用 Local 的時(shí)候,我們說過這是因?yàn)?flask 希望在測試或者開發(fā)的時(shí)候,允許多 app 、多 request 的情況。而 LocalProxy 也是因?yàn)檫@個(gè)才引入進(jìn)來的!

我們拿 current_app = LocalProxy(_find_app) 來舉例子。每次使用 current_app 的時(shí)候,他都會(huì)調(diào)用 _find_app 函數(shù),然后對得到的變量進(jìn)行操作。

如果直接使用 current_app = _find_app() 有什么區(qū)別呢?區(qū)別就在于,我們導(dǎo)入進(jìn)來之后, current_app 就不會(huì)再變化了。如果有多 app 的情況,就會(huì)出現(xiàn)錯(cuò)誤,比如:

  1. from flask import current_app

  2. app = create_app()

  3. admin_app = create_admin_app()

  4. def do_something():

  5.    with app.app_context():

  6.        work_on(current_app)

  7.        with admin_app.app_context():

  8.            work_on(current_app)

這里我們出現(xiàn)了嵌套的 app,每個(gè) with 上下文都需要操作其對應(yīng)的 app,如果不適用 LocalProxy 是做不到的。

對于 request 也是類似!但是這種情況真的很少發(fā)生,有必要費(fèi)這么大的功夫增加這么多復(fù)雜度嗎?

其實(shí)還有一個(gè)更大的問題,這個(gè)例子也可以看出來。比如我們知道 current_app 是動(dòng)態(tài)的,因?yàn)樗澈髮?yīng)的棧會(huì) push 和 pop 元素進(jìn)去。那剛開始的時(shí)候,棧一定是空的,只有在 withapp.app_context() 這句的時(shí)候,才把棧數(shù)據(jù) push 進(jìn)去。而如果不采用 LocalProxy 進(jìn)行轉(zhuǎn)發(fā),那么在最上面導(dǎo)入 from flask import current_app 的時(shí)候, current_app 就是空的,因?yàn)檫@個(gè)時(shí)候還沒有把數(shù)據(jù) push 進(jìn)去,后面調(diào)用的時(shí)候根本無法使用。

所以為什么需要 LocalProxy 呢?簡單總結(jié)一句話:因?yàn)樯舷挛谋4娴臄?shù)據(jù)是保存在棧里的,并且會(huì)動(dòng)態(tài)發(fā)生變化。如果不是動(dòng)態(tài)地去訪問,會(huì)造成數(shù)據(jù)訪問異常。

參考資料


題圖:pexels,CC0 授權(quán)。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
flask
一個(gè) Flask 應(yīng)用運(yùn)行過程剖析
編程語言flask上下文
Python基礎(chǔ)教程:Flask進(jìn)擊篇——Flask運(yùn)行流程
mitmproxy 抓包神器-3.抓取網(wǎng)站數(shù)據(jù)或圖片
tornado.gen 模塊解析
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服