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掉
浙公网安备 33010602011771号