local推导步骤
#多个线程修改同一个数据,复制多份变量给每个线程用,为每个线程开辟一块空间进行数据存储
这里的local实现原理,通过get_ident获取线程ID,通过getcurrent获取协程ID,放到字典中,
#不用local from threading import Thread import time lqz = -1 def task(arg): global lqz lqz = arg time.sleep(2) #最后显示9个相同的数字 print(lqz) for i in range(10): t = Thread(target=task,args=(i,)) t.start() --------------------------------- # 使用local对象 from threading import Thread from threading import local import time # {'线程id1':{'args':1},'线程id2':{'args':2}} #local存的格式如上,根据线程id进行存值 lqz = local() def task(arg): lqz.arg = arg time.sleep(2) print(lqz.arg) for i in range(10): t = Thread(target=task,args=(i,)) t.start() -------------------------------------------- #自定义local,函数版 # {'线程id1': {'args': 1}, '线程id2': {'args': 2}, '线程id3': {'args': 3}} from threading import get_ident,Thread import time storage = {} def set(k,v): #get_ident的作用是获取线程的id号 ident = get_ident() if ident in storage: storage[ident][k] = v else: storage[ident] = {k:v} def get(k): ident = get_ident() return storage[ident][k] def task(arg): set('val',arg) #设置值 v = get('val') #取值 print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start() ---------------------------------------------- #面向对象版的local #{'线程id1': {'args': 1}, '线程id2': {'args': 2}, '线程id3': {'args': 3}} from threading import get_ident,Thread import time class Local(object): storage = {} def set(self, k, v): ident = get_ident() if ident in Local.storage: #类的属性可以点出来 Local.storage[ident][k] = v else: Local.storage[ident] = {k: v} def get(self, k): ident = get_ident() return Local.storage[ident][k] obj = Local() def task(arg): obj.set('val',arg) v = obj.get('val') time.sleep(1) print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start() ---------------------------------------------- #改进版本 from threading import get_ident,Thread import time class Local(object): storage = {} def __setattr__(self, k, v): ident = get_ident() if ident in Local.storage: Local.storage[ident][k] = v else: Local.storage[ident] = {k: v} def __getattr__(self, k): ident = get_ident() return Local.storage[ident][k] obj = Local() # obj2=Local() # obj3=Local() # obj4=Local() # obj5=Local() def task(arg): obj.val = arg time.sleep(1) print(obj.val) for i in range(10): t = Thread(target=task,args=(i,)) t.start() -------------------------------------------- #每次生成local对象用的都是自己的字典,上面那个用的全是类的字典,不管生成几个对象,用的都是同一个 from threading import get_ident,Thread import time class Local(object): def __init__(self): object.__setattr__(self,'storage',{}) # self.storage={},, #这样写会出现递归,所以用是上边的写法 def __setattr__(self, k, v): ident = get_ident() if ident in self.storage: self.storage[ident][k] = v else: self.storage[ident] = {k: v} def __getattr__(self, k): ident = get_ident() return self.storage[ident][k] obj = Local() def task(arg): obj.val = arg # obj.xxx = arg time.sleep(1) print(obj.val) for i in range(10): t = Thread(target=task,args=(i,)) t.start() #到此为止,自己写了一个local对象,只支持线程 --------------------------------------------------- #要支持协程 try: #getcurrent 获取协程id from greenlet import getcurrent as get_ident except Exception as e: from threading import get_ident from threading import Thread import time class Local(object): def __init__(self): object.__setattr__(self,'storage',{}) def __setattr__(self, k, v): ident = get_ident() if ident in self.storage: self.storage[ident][k] = v else: self.storage[ident] = {k: v} def __getattr__(self, k): ident = get_ident() return self.storage[ident][k] obj = Local() def task(arg): obj.val = arg obj.xxx = arg print(obj.val) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
#请求来了执行__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) #点进来 这个方式基于哪个类? 往上层推,基于自己写的app = Flask(__name__) 这个类 def wsgi_app(self, environ, start_response): #flask所有请求上下文基于这几行 ctx = self.request_context(environ) #与ctx.push()一起将请求相关的数据environ封装到了RequestContext对象中,再将对象封装到local中(每个线程/协程独立空间存储) error = None try: try: ctx.push() response = self.full_dispatch_request() #下面这些就是执行当前函数 except Exception as e: error = e response = self.handle_exception(e) except: # noqa: B001 error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error) #把当前请求对象从取出来
ctx = self.request_context(environ) #environ请求相关所有东西 #看这一行,self是当前请求对象,也就是实例化flask的app 点request_context进来 def request_context(self, environ): return RequestContext(self, environ) #返回这个类 #ctx = RequestContext(self, environ) 相当于ctx等于这个类 class RequestContext(object): #self应该是ctx吧 def __init__(self, app, environ, request=None, session=None): self.app = app if request is None: #第一次来肯定是None,看类中传的参数 request = app.request_class(environ) #发现这又是一个类,这一步把 #request重新封装了吧 self.request = request self.url_adapter = None try: self.url_adapter = app.create_url_adapter(self.request) except HTTPException as e: self.request.routing_exception = e self.flashes = None self.session = session ------------------------------------------------------- def request_context(self, environ): """Create a :class:`~flask.ctx.RequestContext` representing a WSGI environment. Use a ``with`` block to push the context, which will make :data:`request` point at this request. See :doc:`/reqcontext`. Typically you should not call this from your own code. A request context is automatically pushed by the :meth:`wsgi_app` when handling a request. Use :meth:`test_request_context` to create an environment and context instead of this method. :param environ: a WSGI environment """ 创建一个:类:‘~ flask.ctx。RequestContext的代表 WSGI环境。使用“with”块来推送上下文, 这将使:data: ' request '指向这个请求。 看到:医生:“/ reqcontext”。 通常,您不应该从自己的代码中调用它。一个请求 上下文被:meth: ' wsgi_app '自动推送 处理一个请求。使用:meth: ' test_request_context '来创建 环境和上下文,而不是这个方法。 :param environment on:一个WSGI环境 """ return RequestContext(self, environ)
#看第二行 ctx.push() #上一行知道ctx=RequestContext(self, environ),所以执行的是这个类的push方法 def push(self): #self是ctx #进来之后先看最后一句 top = _request_ctx_stack.top if top is not None and top.preserved: top.pop(top._preserved_exc) app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, "exc_clear"): sys.exc_clear() _request_ctx_stack.push(self) #这个push方法是_request_ctx_stack对象的类LocalStack的,不是当前类的 #把self添加到_request_ctx_stack中 #来到_request_ctx_stack #下面这些东西都是全局变量,程序开始阶段已经存在 _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_req_object, "request")) session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g")) #看看LocalStack这个类 class LocalStack(object): def __init__(self): #self是_request_ctx_stack #_request_ctx_stack = LocalStack() self._local = Local() #看看Local这个类 #程序刚启动这里的数据是空的,仅仅完成实例化, class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} ------------------------------------ #看完下一个方法在回头看这里 #相当于 name=stack value=[] { 唯一ID:{stack:[ctx]} } #然后看LocalStack的push方法 def push(self, obj): rv = getattr(self._local, "stack", None) #刚来到时候是None if rv is None: self._local.stack = rv = [] #self._local,执行__setattr__ #现在看上边 rv.append(obj) #这一步把当前请求对象添加到列表中,也就是ctx return rv
#此时在自己写的函数中的request,点进去 _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) #request = LocalProxy(partial(_lookup_req_object, "request")) 还是来到全局变量 session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g")) #来到LocalProxy class LocalProxy(object): 里边有很多方法,此时自己写的类中的request相应的操作,会执行当前类中相对应的方法,比如,print(request) 执行_str__方法 #另外执行这些方法时,都执行偏函数LocalProxy(partial(_lookup_req_object, "request")) #来到_lookup_req_object def _lookup_req_object(name): top = _request_ctx_stack.top #_request_ctx_stack也是全局变量 #去localLocalStack找top if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) #来到top, def top(self) try: return self._local.stack[-1]
请求上下文执行流程
#0 flask项目一启动,有6个全局变量 #globals.py中 _request_ctx_stack:LocalStack对象 _app_ctx_stack :LocalStack对象 request : LocalProxy对象 session : LocalProxy对象 #1请求来的时候。先到app.__call__()start_response) #app.py class Flask(_PackageBoundObject): def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response) #执行 #2、点wsgi_app()进去 #2.1执行 ctx = self.request_context(environ) def wsgi_app(self, environ, start_response): #ctx是RequestContext的对象,里面包含了当次请求的request,session ctx = self.request_context(environ) #点request_context error = None try: try: ctx.push() #此时的push其实是RequestContext类的push方法 2.1.1来到 def request_context(self, environ): return RequestContext(self, environ) #把RequestContext返回给了ctx ,所以ctx是RequestContext的对象 #2.2点 RequestContext进去 #ctx.py ,先看push方法 class RequestContext(object): def push(self): _request_ctx_stack.push(self) # 前面知道_request_ctx_stack是全局变量 #知道ctx是RequestContext类的对象,所以此时self是ctx, #2.2.1点_request_ctx_stack进去 #globals.py _request_ctx_stack = LocalStack() #进去找push方法 #2.2.2 #local.py class LocalStack(object): def push(self, obj): #传进来的obj就是ctx rv = getattr(self._local, "stack", None) #self._local是init的时候,实例化进来的,也就是flask定义的local()对象可以点进去看看self._local = Local(),local和上面推导的哟杨 #一开始没有stack if rv is None: self._local.stack = rv = [] rv.append(obj) return rv #此时{"线程id:{'static':[ctx]},"线程id2:{'static':[ctx]}} #也就是把ctx放到local对象里 #那么,往回看, _request_ctx_stack = LocalStack() #相当于这句话完成把ctx放到local里 #再往回看 ctx.push() #也就是这句话完成的
print(request)
#其实走的是LocalProxy类的__ste__ request = LocalProxy(partial(_lookup_req_object, "request")) #点进去找__repr__,先把他理解为__str__ def __repr__(self): try: obj = self._get_current_object() #点进去 if not hasattr(self.__local, "__release_local__"): return self.__local() #是从__init__传过来的,隐藏属性传的 #self.__local就是传入的偏函数,偏函数的执行结果是request对象 def __init__(self, local, name=None): object.__setattr__(self, "_LocalProxy__local", local) object.__setattr__(self, "__name__", name) # return self.__local() ,此时i相当于偏函数partial(_lookup_req_object, "request")加括号执行,那么点_lookup_req_object进去 def _lookup_req_object(name): #name ‘request’ top = _request_ctx_stack.top #top就是ctx的对象 if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) #返回的就是request对象 也就是打印的request -------------------------------- #那么 print(request.method) 用点属性的时候,又是怎么执行的呢 通过__grtattr__方法实现 def __getattr__(self, name): if name == "__members__": return dir(self._get_current_object()) return getattr(self._get_current_object(), name) #通过反射找出点的内容
flask有一个请求上下文,还有一个应用上下文
#ctx: -是:RequestContext对象:封装了request和session -调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置 #app_ctx: -是:AppContext(self) 对象:封装了当前的app和g -调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
g对象
#g对象是什么 专门用来存储用户信息的g对象,g的全称的为global g对象在一次请求中的所有的代码的地方,都是可以使用的 #g对象和session的区别 session对象是可以跨request的,只要session还未失效,不同的request的请求会获取到同一个session,但是g对象不是,g对象不需要管过期时间,请求一次就g对象就改变了一次,或者重新赋值了一次
from flask import Flask,g,request app = Flask(__name__) @app.before_request #好像必须与它一起用 def xxxx(): g.lqz='lqz' # request.lqz='lqz' 效果与g一样,估计作者是怕误操作 request.method='lqz' @app.route('/',methods=['GET','POST']) def index(): print(g.lqz) return 'dddddd' if __name__ == '__main__': app.run(port=8888)
偏函数
#偏函数 #提前给函数传值,以后就可以少传值了 from functools import partial def add(a,b,c,d): return a+b+c+d # print(add(1,2,3,4)) add=partial(add,1,2) print(add(3,4)) >>10