flask中的上下文分两种,application context和request context,即应用上下文和请求上下文。python
从名字上看,可能会有误解,认为应用上下文是一个应用的全局变量,全部请求均可以访问修改其中的内容;而请求上下文则是请求内可访问的内容。
但事实上,这二者并非全局与局部的关系,它们都处于一个请求的局部中。web
先说结论:每一个请求的g都是独立的,而且在整个请求内都是可访问修改的。面试
下面来研究一下。redis
上下文类的定义:sql
上下文类定义在flask.ctx模块中shell
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()数据库
# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0django
查看了源代码,AppContext类便是应用上下文,能够看到里面只保存了几个变量,其中比较重要的有:
app是当前web应用对象的引用,如Flask;还有g,用来保存须要在每一个请求中须要用到的请求内全局变量。编程
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = Noneflask
# Request contexts can be pushed multiple times and interleaved with
# other request contexts. Now only if the last level is popped we
# get rid of them. Additionally if an application context is missing
# one is created implicitly so for each level we add this information
self._implicit_app_ctx_stack = []
RequestContext即请求上下文,其中有咱们熟悉的request和session,app和应用上下文中的app含义相同。
上下文对象的做用域
那么这两种上下文运行时是怎么被使用的呢?
线程有个叫作ThreadLocal的类,也就是一般实现线程隔离的类。而werkzeug本身实现了它的线程隔离类:werkzeug.local.Local。LocalStack就是用Local实现的。
在flask.globals模块中定义了两个LocalStack对象:
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
LocalStack是flask定义的线程隔离的栈存储对象,分别用来保存应用和请求上下文。
它是线程隔离的意思就是说,对于不一样的线程,它们访问这两个对象看到的结果是不同的、彻底隔离的。这是根据pid的不一样实现的,相似于门牌号。
而每一个传给flask对象的请求,都是在不一样的线程中处理,并且同一时刻每一个线程只处理一个请求。因此对于每一个请求来讲,它们彻底不用担忧本身上下文中的数据被别的请求所修改。
而后就能够解释这个特性:从flask模块中引入的g、session、request、current_app是怎么作到同一个对象能在全部请求中使用而且不会冲突。
这几个对象仍是定义在flask.globals中:
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类的构造函数接收一个callable参数,上面这几个就传入了一个偏函数。以g为例,当对g进行操做时,就会调用做为参数的偏函数,并把操做转发到偏函数返回的对象上。
查看这几个函数的实现:
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return getattr(top, name)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app
因为_app_ctx_stack和_request_ctx_stack都是线程隔离的,因此对g的调用就是这样一个过程:
访问g-->从当前线程的应用上下文栈顶获取应用上下文-->取出其中的g对象-->进行操做。
因此能够经过一个g对象而让全部线程互不干扰的访问本身的g。
上下文对象的推送
构建Flask对象后并不会推送上下文,而在Flask对象调用run()做为WSGI 应用启动后,每当有请求进入时,在推送请求上下文前,若是有必要就会推送应用上下文。但运行了run就会阻塞程序,因此在shell中调试时,必须手动推送上下文;或者使用flask-scripts,它运行的任务会在开始时自动推送。
上面加粗的“若是有必要”,那么什么叫有必要呢?是否是意味着在每一个线程里应用上下文只会被推送一次、一次请求结束下一次请求来的时候就不用再推送应用上下文了呢?
来看RequestContext的源码,push函数:
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# Before we push the request context we have to ensure that there
# is an application context.
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)
_request_ctx_stack.push(self)
flask在推送请求上下文的时候调用push函数,他会检查当前线程的应用上下文栈顶是否有应用上下文;若是有,判断与请求上下文是否属于同一个应用。在单WSGI应用的程序中,后者的判断无心义。
此时,只要没有应用上下文就会推送一个当前应用的上下文,而且把该上下文记录下来。
请求处理结束,调用auto_pop函数,其中又调用自身的pop函数:
def pop(self, exc=None):
app_ctx = self._implicit_app_ctx_stack.pop()
………………
………………
rv = _request_ctx_stack.pop()
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
会把请求上下文和应用上下文都pop掉。
故,在单WSGI应用环境下,每一个请求的两个上下文都是彻底独立的(独立于线程上曾经的请求,独立于其余线程的请求)。Q.E.D
那么,何时不必推送呢?事实上,每次请求到来的时候都会推送,都是有必要的。由于当Flask在做为WSGI应用运行的时候,不可能出现当前线程的应用上下文已存在的状况。
那么就要搞清何时会有已存在的应用上下文。
研究时我参考了博文:https://blog.tonyseek.com/post/the-context-mechanism-of-flask/
该博文在最后提到了“两个疑问”:①应用和请求上下文在运行时都是线程隔离的,为什么要分开来?②每一个线程同时只处理一个请求,上下文栈确定只有一个对象,为什么要用栈来存储?
博主认为,这两个设计都是为了在离线状态下调试用:
因此,综上所述,在非离线状态下,上下文栈在每一个WSGI应用里是独立的,而每一个应用里线程同时只处理一个请求,故上下文栈确定只有一个对象。而且,在请求结束后都会释放,因此新的请求来的时候都会从新推送两个上下文。
小结:
解释了这么多,对于flask编程来讲,只有一个应用上的结论:每一个请求的g都是独立的,而且在整个请求内都是可访问修改的。
一、threding-local
做用:为每个线程开辟一块空间进行数据存储 from threading import local from threading import Thread import time # 示例化local对象 ret=local() def task(s): global ret ret.value=s time.sleep(2) print(ret.value) # 开启10个线程 for i in range(10): t=Thread(target=task,args=(i,)) t.start()
二、自定义local
# 若是有协程则使用协程惟一标识getcurrent try: from greenlet import getcurrent as get_ident except Exception as e: from threading import Thread,get_ident class Local(object): # 线程的惟一标识 ident = get_ident() def __init__(self): # 执行父类__setattr__ object.__setattr__(self,"storage",{}) def __setattr__(self,k, v): """ 构造dict storage={ ident:{val:0}, ident:{val:1}, ident:{val:3}, ident:{val:4}, } """ if self.ident in self.storage: self.storage[self.ident][k] = v else: self.storage[self.ident] = {k: v} def __getattr__(self,k): return self.storage[self.ident][k] obj=Local() def task(arg): # 执行__setattr__ obj.var=arg # 执行__getattr__ v=obj.var print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
1、上下文管理本质(相似于threading.local) 1、每个线程都会在Local类中建立一条数据 { “惟一标识”:{stark:[ctx,]} “惟一标识”:{stark:[ctx,]} } 2、当请求进来以后,将请求相关数据添加到列表里面[request,],之后若是使用时,就去读取 3、列表中的数据,请求完成以后,将request从列表中移除 2、在源码中分析上下文管理 第一阶段:执行__call__--->app.wsgi-->将ctx(request,session)封装为RequestContent()在(open_session), app_ctx(g,app)封装为APPContent()经过LocalStack将这两个类放入Local对象中 第二阶段:视图函数导入:request/session/g/app ,经过偏函数(_lookup_req_object)在经过(LocalProxy())去LocalStack中的Local类中对其进行增删改查操做 第三阶段:请求处理完毕 - 经过save_session将签名session保存到cookie -经过ctx.pop()去LocalStack中的Local类- 将ctx删除
问题一:flask和django的区别: 对于django来讲,内部组件特别多,自身功能强大,有点大而全,而flask,内置组件不多,可是它的第三方组件不少,扩展性强,有点短小精悍,而它们之间也有类似之处, 由于它们两个框架都没有写sockte,都是基于wsgi协议作的,在此以外,flask框架中的上下文管理较为耀眼。 相同点:它们两个框架都没有写sockte,都是基于wsgi协议作的 请求相关数据传递的方式不一样:django:经过传递request参数取值 flask:见问题二 组件不一样:django组件多 flask组件少,第三方组件丰富 问题1.1: flask上下文管理: 简单来讲,falsk上下文管理能够分为三个阶段: 1、请求进来时,将请求相关的数据放入上下问管理中 2、在视图函数中,要去上下文管理中取值 3、请求响应,要将上下文管理中的数据清除 详细点来讲: 1、请求刚进来,将request,session封装在RequestContext类中,app,g封装在AppContext类中,并经过LocalStack将requestcontext和appcontext放入Local类中 二、视图函数中,经过localproxy--->偏函数--->localstack--->local取值 3、请求相应时,先执行save.session()再各自执行pop(),将local中的数据清除 问题1.2 flask第三方组件 flask: -flask-session 默认放入cookie,能够放入redis -flask-redis -flask-migrate -flask-script -blinker 信号 公共: DBUtils 数据库链接池 wtforms 表单验证+生成HTML标签 sqlalchemy 自定义:Auth 参考falsk-login 问题二:Flask中的session是何时建立,何时销毁的? 当请求进来时,会将requset和session封装为一个RequestContext对象,经过LocalStack将RequestContext放入到Local对象中,由于 请求第一次来session是空值,因此执行open_session,给session(uuid4())赋值,再经过视图函数处理,请求响应时执行save.session,将签名session写入cookie中,再讲Local中的数值pop掉。 问题三:flask中一共有几个LocalStack和Local对象 两个LocalStack,两个Local request、session共同用一个LocalStack和Local g、app共同用一个Localstack和Local 问题四: 为何把请求放到RequestContext中: 由于request和session都是在视图中操做频繁的数据,也是用户请求须要用的数据,将request和session封装在RequestContext中top,pop一次就能够完成,而单独不封装在一块儿就会屡次操做, ctx = RequestContext(request,session) 问题五:local做用 -保存 请求上下文对象和app上下文对象 -localstack的源码与threading.local(线程处理)做用类似,不一样之处是Local是经过greenlet(协程)获取惟一标识,粒度更细 问题六:Localstack做用 2、将local对象中的数据维护成一个栈【ctx,ctx】(先进后出) { “协程或线程的惟一标识”: { stack:[ctx,ctx,ctx,] }
}
为何维护成一个栈?
当是web应用时:不论是单线程仍是多线程,栈中只有一个数据
- 服务端单线程:
{
111:{stack: [ctx, ]}
}
- 服务端多线程:
{
111:{stack: [ctx, ]}
112:{stack: [ctx, ]}
}
离线脚本:能够在栈中放入多个数据
with app01.app_context():
print(current_app)
with app02.app_context():
print(current_app)
print(current_app)
问题七:什么是g?
g 至关于一次请求的全局变量,当请求进来时将g和current_app封装为一个APPContext类,在经过LocalStack将Appcontext放入Local中,取值时经过偏函数,LocalStack、loca l中取值,响应时将local中的g数据删除:
问题八:怎么获取Session/g/current_app/request
经过 、偏函数(lookup_req_object)、Localstack、Local取值
问题九: 技术点:
- 反射 (LocalProxy())
- 面向对象,封装:RequestContext
- 线程(threading.local)
- 笔试:本身写一个类+列表 实现栈。(LocalStack)
问题十:python基本哪些内容比较重要:
一、反射
-CBV
-django配置文件
-wtforms中的Form()示例化中 将"_fields中的数据封装到From类中"
二、装饰器 (迭代器,生成器)
-flask:路由、装饰器
-认证
-csrf
三、面向对象
-继承、封装、多态(简单描述)
-双下划线:
__mro__ wtform中 FormMeta中继承类的优先级
__dict__
__new__ ,实例化可是没有给当前对象
wtforms,字段实例化时返回:不是StringField,而是UnboundField
rest frawork many=Turn 中的序列化
__call__
flask 请求的入口app.run()
字段生成标签时:字段.__str__ => 字段.__call__ => 插件.__call__
__iter__ 循环对象是,自定义__iter__
wtforms中BaseForm中循环全部字段时定义了__iter__
metaclass - 做用:用于指定当前类使用哪一个类来建立 - 场景:在类建立以前定制操做 示例:wtforms中,对字段进行排序。