flask之上下文管理机制

预备知识:

  1、threading.local:为每一个线程开辟一块独立的空间,解决多个线程同时修改数据出错

  2、面向对象相关:

    - 多个对象封装到一个类
    - __call__方法:
        xx():
          - 函数或者方法(区分,type)
          - 类:实例化
          - 对象:执行__call__方法
    - 特殊的双下划线方法:(flask的LocalProxy中全部使用)
      __init__
        __new__
        __call__
        __setattr__
        __getattr__
        __setitem__
        __getitem__
        __enter__
        __exit__
        __add__

    - 私有字段
        - 内部可以访问,外部不可以访问
        - 强制调用私有字段:
            _类名__字段名
        - 派生类(子类)中无法调用父类(基类)的私有字段

  3、偏函数

import functools

def func(a1,a2):
    print(a1,a2)

new_func = functools.partial(func,a1)   # 如果这里传的是位置参数,下面调用也要按位置传参
        
new_func(a2)  # 等价执行func函数

 

源码剖析:

  上下文:

    - 请求上下文:RequestContext类对象ctx

    - 应用上下文:AppContext类对象app_ctx

  flask的上下文实现的概述:

    - threading.local:为每一个线程开辟一块空间
        flask自己实现了Local类:其中创建了一个字典({greelet做唯一标识:存数据})保证数据隔离
    - 请求进来:
         - 请求相关的数据封装到RequestContext类
         - 再将RequestContext对象添加到Local类中(通过LocalStark将对象添加到Local对象中)
    - 使用,调用request
         - 调用此类方法,request.method,print(request),request+xxx,调用LocalProxy中对应方法
         - 函数
         - 通过LocalStark去Local获取值
    - 请求中止
         - 通过LocalStark的pop方法把Local中的值移除

  详细讲解: 

  1、 app.__call__ :请求处理入口

  2、 app.wsgi_app() : 所有的处理机制

     源码:

 1     def wsgi_app(self, environ, start_response):
 2         # 1、在request_context这里,实例化RequestContext()对象,environ是请求的相关所有数据
 3         # 封装了request,session,app
 4         ctx = self.request_context(environ)
 5 
 6         # 2、执行RequestContext()对象的push
 7         # push操作之后,session就有值了
 8      ctx.push()
 9         # push内部创建了app_ctx:AppContext(self)对象,
10 
11         error = None
12         # try的内部其实是在做视图函数的处理
13         try:
14             try:
15                 response = self.full_dispatch_request() # 这里找视图函数并执行
16             except Exception as e:
17                 error = e
18                 response = self.handle_exception(e) # 当在处理视图的时候出错执行这个函数
19             except:
20                 error = sys.exc_info()[1]
21                 raise
22             # 给用户返回信息
23             return response(environ, start_response)
24         finally:
25             if self.should_ignore_error(error):
26                 error = None
27             # 请求结束:执行RequestContext()对象的auto_pop函数
28             ctx.auto_pop(error)

 

    2-1、 ctx = self.request_context(environ) ,实例化RequestContext类对象

1     def request_context(self, environ):
2         # 把请求相关数据封装到RequestContext()类中
3         return RequestContext(self, environ)

 

    2-1-a、在RequestContext类中:封装了app,request,session等字段,而这里的request字段又是Request类的对象,二次封装

 1 def __init__(self, app, environ, request=None):
 2     self.app = app  # app就是当前实例化Flask对象
 3     if request is None:
 4         # 请求进来只传了前两个参数,request是空的
 5         # 在request_class中实例化了Request类的对象
 6         request = app.request_class(environ)
 7     self.request = request
 8     self.url_adapter = app.create_url_adapter(self.request)
 9     self.flashes = None
10     self.session = None

    2-2、 ctx.push() 

   在这里面有两个字段_request_ctx_stack_app_ctx_stack,这两个字段均是LocalStark类的实例对象

   而在LocalStark类中,又封装了Local类的对象,

 1 def push(self):
 2     top = _request_ctx_stack.top  # 请求一开始进来,为空
 3     if top is not None and top.preserved:
 4         top.pop(top._preserved_exc)
 5   # _app_ctx_stark是LocalStark的对象
 6     app_ctx = _app_ctx_stack.top  # 请求一开始进来,为空
 7     if app_ctx is None or app_ctx.app != self.app:
 8         # AppContext(self)对象,
 9         # app_ctx.g     理解为字典,当在请求扩展时需要临时存取数据的时候使用,仅限一次使用
10         app_ctx = self.app.app_context()
11         app_ctx.push()
12         self._implicit_app_ctx_stack.append(app_ctx)
13     else:
14         self._implicit_app_ctx_stack.append(None)
15 
16     if hasattr(sys, 'exc_clear'):
17         sys.exc_clear()
18 
19     # _request_ctx_stack就是LocalStark的对象
20     _request_ctx_stack.push(self)
21 
22     # 获取session信息
23     self.session = self.app.open_session(self.request)
24     if self.session is None:
25         self.session = self.app.make_null_session()

    2-2-a: app_ctx = self.app.app_context() 

      实例化了一个AppContext类对象,在这个类里面,封装了app,g等字段

1 def app_context(self):
2     
3     return AppContext(self)

 

    2-2-b: app_ctx.push() 

1 def push(self):
2     self._refcnt += 1
3     if hasattr(sys, 'exc_clear'):
4         sys.exc_clear()
5     # _app_ctx_stack是LocalProxy对象,又创建了一个Local对象
6     _app_ctx_stack.push(self)
7     appcontext_pushed.send(self.app)

    2-2-c: _app_ctx_stack.push() ,这里的处理机制就是和ctx.push()中的_request_ctx_stack.push一样了

 

1 def push(self, obj):
2     # 获取stark,但是没有
3     rv = getattr(self._local, 'stack', None)
4     if rv is None:
5         # _local是Local()的对象,对象+.=xx,执行setattr方法
6         # 执行Local()的setattr方法
7         self._local.stack = rv = []
8     rv.append(obj)  # obj就是RequestContext()类或者AppContext类()的对象,也就是ctx或者app_ctx
9     return rv

 

    2-2-d: _request_ctx_stack.push() 

1 def push(self, obj):
2     # 获取stark,但是没有
3     rv = getattr(self._local, 'stack', None)
4     if rv is None:
5         # _local是Local()的对象,对象+.=xx,执行setattr方法
6         # 执行Local()的setattr方法
7         self._local.stack = rv = []
8     rv.append(obj)  # obj就是RequestContext()类或者AppContext类()的对象,也就是ctx或者app_ctx
9     return rv

ps:到这里我们发现其实每一个线程进来是创建了两个Local对象的,一个存放的是request和session等信息,一个存放的是app和g等字段信息 

    2-2-e:最后处理session信息

1 self.session = self.app.open_session(self.request)
2 if self.session is None:
3     self.session = self.app.make_null_session()

    2-3: self.full_dispatch_request() 在这里处理所有的视图相关内容

 1 def full_dispatch_request(self):
 2     # before_first_request,这里处理的是否是第一次请求
 3     self.try_trigger_before_first_request_functions()
 4     try:
 5         # 触发request_started信号
 6         request_started.send(self)
 7 
 8         # before_request,处理机制
 9         rv = self.preprocess_request()
10         if rv is None:
11             # 执行视图函数
12             rv = self.dispatch_request()
13     except Exception as e:
14         rv = self.handle_user_exception(e)
15     return self.finalize_request(rv)

    2-3-a: self.dispatch_request()

 1 def dispatch_request(self):
 2     req = _request_ctx_stack.top.request
 3     if req.routing_exception is not None:
 4         self.raise_routing_exception(req)
 5     rule = req.url_rule
 6 
 7     if getattr(rule, 'provide_automatic_options', False) \
 8        and req.method == 'OPTIONS':
 9         return self.make_default_options_response()
10 
11     # 最终是这里执行视图函数
12     return self.view_functions[rule.endpoint](**req.view_args)

 

   3、当我们在视图函数中使用request,session的时候,是如何实现的?(以request为例)

     3-1、当我们直接使用request的时候,其实是在执行 request.__str__ 方法,先看request在flask中是个什么东西,

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

      发现它是LocalProxy类的对象(在globals.py文件中,partial,这个是偏函数,文章开始有介绍,这里不再赘述)

    3-2、执行LocalProxy(partial(_lookup_req_object, 'request'))

      LocalProxy类的构造方法:利用setattr创建了__local,这样一个私有字段,

1 def __init__(self, local, name=None):
2     # local就是传入的偏函数
3     # (self, '_LocalProxy__local', local) --> 等价self.__loacl = local
4     object.__setattr__(self, '_LocalProxy__local', local)
5 
6     object.__setattr__(self, '__name__', name)
7     if callable(local) and not hasattr(local, '__release_local__'):
8         object.__setattr__(self, '__wrapped__', local)

    3-3、根据对request的操作不同执行对应的方法,例如:request  request.method   request+xxx等

    __str__方法:

1 __str__ = lambda x: str(x._get_current_object())    
2 # x就是当前的对象request,执行_get_current_object方法,返回str(LocalProxy._get_current_object)

    __getattr__方法:

def __getattr__(self, name):
    # request.method  ---> name = method
    if name == '__members__':
        return dir(self._get_current_object())
    # 执行_get_current_object方法,拿到ctx.request,然后执行ctx.request.method
    return getattr(self._get_current_object(), name)

    3-4、 _get_current_object() 

1 def _get_current_object(self):
2     if not hasattr(self.__local, '__release_local__'):
3         return self.__local()   # 执行传进来的偏函数,结果是ctx.request
4     try:
5         return getattr(self.__local, self.__name__) # self.__local就是传进来的偏函数,name是空
6     except AttributeError:
7         raise RuntimeError('no object bound to %s' % self.__name__)

    3-5、执行传进来的偏函数(以request为例)

1 def _lookup_req_object(name):
2     # name = request
3     # _request_ctx_stack是LocalStack的对象,执行top
4     # 返回值是ctx,即RequestContext()类的对象
5     top = _request_ctx_stack.top    # top = ctx   ctx是RequestContext的对象
6     if top is None:
7         raise RuntimeError(_request_ctx_err_msg)
8     return getattr(top, name)   # _request_ctx_stack.top.request ---> ctx.request

    我们发现其实它就是通过LocalStack类找到RequestContext类的对象,然后从里面获取封装的request,session等信息

  4、当请求结束后,我们做的操作(源码在上面均可找到,这里不再展示)

    4-1、请求结束:执行RequestContext()对象的auto_pop函数
           ctx.auto_pop(error) 
    4-2、执行RequestContext()对象的的pop函数
           ctx.pop(exc) 
    4-3、 _request_ctx_stack.pop() 
          _request_ctx_stack是LocalStark的对象,执行LocalStark的对象的pop函数
    4-4、 stack.pop() 
          stark就是我们存到标识下的那个列表
          执行stark的pop方法,把存进去的那个RequestContext()类的对象pop掉

    

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @ 2018-03-27 17:16  chitalu  阅读(211)  评论(0)    收藏  举报