1. 程式人生 > >Flask上下文管理機制流程(原始碼剖析)

Flask上下文管理機制流程(原始碼剖析)

Flask請求上下文管理

1 偏函式

  • partial 使用該方式可以生成一個新函式

    from functools import partial
    
    def mod( n, m ):
      return n % m
    
    mod_by_100 = partial( mod, 100 )  # 100傳給n
    
    print mod( 100, 7 )  # 2
    print mod_by_100( 7 )  # 2

2 執行緒安全

import time
from threading import local

class Foo(local): 
# 繼承local,保證執行緒安全,也保證了處理速度,threading.local()這個方法的特點用來儲存一個全域性變數,但是這個全域性變數只有在當前執行緒才能訪問,
    num = 0
    
foo = Foo()
def add(i):
    foo.num =i
    time.sleep(0.5)
    print(foo.num)

from threading import Thread
for i in range(20):
    task = Thread(target=add,args=(i,))
    task.start()
    

3 請求上下文

3.1 Flask請求上文

  1. 當請求進來時,app(), Flask例項化物件app**執行__call__**
def __call__(self, environ, start_response):
    """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
    return self.wsgi_app(environ, start_response)
  1. 執行wsgi_app 得到 一個 RequestContext的物件 ctx (封裝了request以及session)

    ctx = self.request_context(environ)
    class RequestContext(object):
        #此時的self是RequestContext物件 -->ctx 中封裝了request/session
        def __init__(self, app, environ, request=None):
            self.app = app      #app = Flask物件
            if request is None:
                #請求的原始資訊通過request_class後此時request已經存在,request.methods等
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None
  2. ctx 執行 ctx.push():

    ctx = self.request_context(environ)
            error = None
            try:
                try:
                    ctx.push()
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
  3. RequestContext物件的push方法

    def push(self):
            # _request_ctx_stack = LocalStack()一個LocalStack物件
            # _request_ctx_stack._local = LocalStack()._loacl = {"__storage__":{},"__ident_func__":get_ident}
            top = _request_ctx_stack.top
            #top =None
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
    • _ request_ctx_stack是一個LocalStack物件 ,LocalStack()._local是一個Local物件 即Local()
    class LocalStack(object):
        def __init__(self):
            self._local = Local()
            #self._loacl = {"__storage__":{},"__ident_func__":get_ident}
    • _request_ctx_stack中top方法,返回None (想哭,但是沒有眼淚,這個方法,琢磨了半個小時)
  4. Local物件經過初始化得到的字典值

    class Local(object):
        #限定鍵槽,當前只能由兩個屬性值__storage__,__ident_func__
        __slots__ = ('__storage__', '__ident_func__')
    
        def __init__(self):
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)
    
            # {"__storage__":{},"__ident_func__":get_ident}  #此時get_dient 是個沒有執行的函式,記憶體地址
    • _request_ctx_stack中top方法,返回None (第二次不會上當)
    @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try:
                # self._local 即Local物件呼叫__getattr__方法
                #在下文時候Local物件{"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}
                # [ctx->request/session]
                return self._local.stack[-1]
                #得到ctx物件
            except (AttributeError, IndexError):
                return None
  5. _request_ctx_stack 物件執行push方法

    _request_ctx_stack.push(self)  #當前的self為ctx
    def push(self, obj):
            #此時的self是LocalStack物件, obj為ctx
            """Pushes a new item to the stack"""
            # self._local = {"__storage__":{},"__ident_func__":get_ident}
            #找不到返回值是None
            rv = getattr(self._local, 'stack', None)
            if rv is None:
                #由於.stack後面有等號,執行的時候Local()物件的__setattr__方法
                #實際上是地址的賦值,此時stack和rv都指向空列表
                self._local.stack = rv = []
                #{"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}
            rv.append(obj)
            # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}
            # 應用上下文時候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}
            return rv
            #rv=[ctx->request/session]
    def __setattr__(self, name, value):
            #name=stack   value=rv=[]
            #self是Local物件 {"__storage__":{},"__ident_func__":get_ident}
            ident = self.__ident_func__() #執行get_ident函式獲取當前執行緒id 8080
            storage = self.__storage__  #storge ={8080:{stack:rv=[]}}
            try:
                storage[ident][name] = value
            except KeyError:
                storage[ident] = {name: value}   #storage={}
    
             # {"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}
    • 執行完push方法 請求上文結束:
    #當請求進來,第一件事就是要把當前這個請求在我伺服器上的執行緒開闢一個空間(執行緒對應的空間,必須含有stack對應一個列表存放ctx(request/session)
    # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}

3.3.2Flask請求下文

  1. 匯入request開始使用,在request中

    #此時request是一個函式包裹一個偏函式   LocalProxy()是一個代理
    #當前的request是一個LocalProxy()  request.method  執行__getattr__方法
    request = LocalProxy(
        partial(_lookup_req_object, 'request')   #return request物件
    )
  2. 在偏函式中 將request傳入到 _lookup_req_object中: 此時得到一個request物件

    def _lookup_req_object(name):
        # _request_ctx_stack是LocalStack物件
        top = _request_ctx_stack.top
        #下文[ctx->request/session]
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        #此時的name是request,從ctx物件中找出request物件
        return getattr(top, name)

    ...

    @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try:
                # self._local 即Local物件呼叫__getattr__方法
                #在下文時候Local物件{"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}
                # [ctx->request/session]
                return self._local.stack[-1]
                #得到ctx物件
            except (AttributeError, IndexError):
                return None
    • 此時的top不是None已經存在值 (0.0)
  3. partial(_lookup_req_object, 'request') 這一層執行完得到一個reauest物件,將偏函式傳入到LocalProxy中

    @implements_bool
    class LocalProxy(object):
        __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
    
        def __init__(self, local, name=None):
            #local是request偏函式
            object.__setattr__(self, '_LocalProxy__local', local)   #__local = request偏函式
            object.__setattr__(self, '__name__', name)
            #當前偏函式可以執行而且判斷loacl中是否有 __release_local__  ==>這句話成立
            if callable(local) and not hasattr(local, '__release_local__'):
                # "local" is a callable that is not an instance of Local or
                # LocalManager: mark it as a wrapped function.
                object.__setattr__(self, '__wrapped__', local)  #__warpped__還是local偏函式
  4. 當前的request是一個LocalProxy() request.method 執行LocalProxy中的__getattr__方法

        def __getattr__(self, name): # name是method(舉例)
            if name == '__members__':
                return dir(self._get_current_object())
            #此時self._get_current_object()是經過_local 執行後得到的request物件,從request物件中去取出method
            return getattr(self._get_current_object(), name)

    ...

    def _get_current_object(self):
            #self._local是偏函式
            if not hasattr(self.__local, '__release_local__'):
                #執行偏函式,返回request物件
                return self.__local()
            try:
                return getattr(self.__local, self.__name__)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.__name__)

3.3.3小結

由此看來,falsk上下文管理可以分為三個階段:

  1. 請求上文 ->
    當請求進來,第一件事就是要把當前這個請求在伺服器上的執行緒開闢一個空間(執行緒對應的空間,必須含有stack對應一個列表存放ctx(request/session),具體-->:將request,session封裝在 RequestContext類中
    app,g封裝在AppContext類中,並通過LocalStack將requestcontext和appcontext放入Local類中
    在local類中,以執行緒ID號作為key的字典,
  2. 請求下文:
    通過localproxy--->偏函式--->localstack--->local取值
  3. '請求響應時':-->要將上下文管理中的資料清除
    先執行save.session()再各自執行pop(),將local中的資料清除

詳細看原始碼

3.4 應用上下文

  • 執行wsgi_app方法

       #ctx為一個RequestContext的物件,引數為environ
            ctx = self.request_context(environ)
            error = None
            try:
                try:
                    ctx.push()
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
  • 執行push方法,_app_ctx_stack 同樣是LocalStck物件,初始化時候top為None

        def push(self):
            app_ctx = _app_ctx_stack.top
            #app_ctx = None
            if app_ctx is None or app_ctx.app != self.app:
                app_ctx = self.app.app_context()   #app_context是AppContext物件  與RequestContenx一樣,知識序列化出app和g
                app_ctx.push()
                # 應用上文時候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}
                self._implicit_app_ctx_stack.append(app_ctx)
            else:
                self._implicit_app_ctx_stack.append(None)
    • 執行app_ctx.push 進而 **_app_ctx_stack.push**
       def push(self):
            """Binds the app context to the current context."""
            self._refcnt += 1
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
                #將AppContext存在LocalStack物件中
            _app_ctx_stack.push(self)
            appcontext_pushed.send(self.app)
        def push(self, obj):
            #此時的self是LocalStack物件, obj為ctx
            """Pushes a new item to the stack"""
            # self._local = {"__storage__":{},"__ident_func__":get_ident}
            #找不到返回值是None
            rv = getattr(self._local, 'stack', None)
            if rv is None:
                #由於.stack後面有等號,執行的時候Local()物件的__setattr__方法
                #實際上是地址的賦值,此時stack和rv都指向改空列表
                self._local.stack = rv = []
                #{"__storage__":{8080:{stack:rv=[]}},"__ident_func__":get_ident}
            rv.append(obj)
            # {"__storage__":{8080:{stack:rv=[ctx->request/session]}},"__ident_func__":get_ident}
            # 應用上下文時候{"__storage__":{8080:{stack:rv=[app_ctx->(app/g)]}},"__ident_func__":get_ident}
            return rv
            #rv=[ctx->request/session]

    到此,push完畢,應用上文結束,應用下文在離線指令碼時候使用,另外:在global.py中

def _find_app():
    top = _app_ctx_stack.top    #得到app_ctx(app / g)
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app  #返回一個app即flask物件 只不過此時的flask物件 是公共的,與初始化的相同
    # 但是是獨立出來已經被配置好的Flask物件

# LocalStack是 針對當前這個執行緒對獨立的Flask_app進行修改, 不影響現在執行的app  =>離線指令碼
#但是這個app 在請求結束後會從LocalStack中通過 __delattr__ 刪除


# context locals
_request_ctx_stack = LocalStack()  #LocalStark = self._loacl = {"__storage__":{},"__ident_func__":get_ident}
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)   # current_app可以點 .run |  .route 等