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 请求上文
当请求进来时, 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)
执行 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
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)
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 (想哭, 但是没有眼泪, 这个方法, 琢磨了半个小时)
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
_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 请求下文
导入 request 开始使用, 在 request 中
- # 此时 request 是一个函数包裹一个偏函数 LocalProxy() 是一个代理
- # 当前的 request 是一个 LocalProxy() request.method 执行__getattr__方法
- request = LocalProxy(
- partial(_lookup_req_object, 'request') #return request 对象
- )
在偏函数中 将 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)
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 偏函数
当前的 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 上下文管理可以分为三个阶段:
请求上文 ->
当请求进来, 第一件事就是要把当前这个请求在服务器上的线程开辟一个空间 (线程对应的空间, 必须含有 stack 对应一个列表存放 ctx(request/session), 具体 -->: 将 request,session 封装在 RequestContext 类中
App,g 封装在 AppContext 类中, 并通过 LocalStack 将 requestcontext 和 appcontext 放入 Local 类中
在 local 类中, 以线程 ID 号作为 key 的字典,
请求下文:
通过 localproxy---> 偏函数 --->localstack--->local 取值
'请求响应时':--> 要将上下文管理中的数据清除
先执行 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 等
来源: https://www.cnblogs.com/bigox/p/11652859.html