在Flask中处理请求时,应用会生成一个“请求上下文”对象。整个请求的处理过程,都会在这个上下文对象中进行。这保证了请求的处理过程不被干扰。处理请求的具体代码如下:

def wsgi_app(self, environ, start_response):with self.request_context(environ):# with语句中生成一个`response`对象...return response(environ, start_response)

在Flask 0.9版本之前,应用只有“请求上下文”对象,它包含了和请求处理相关的信息。同时Flask还根据werkzeug.local模块中实现的一种数据结构LocalStack用来存储“请求上下文”对象。这在{% post_link 一个Flask应用运行过程剖析 一个Flask应用运行过程剖析 %}中有所介绍。在0.9版本中,Flask又引入了“应用上下文”的概念。本文主要Flask中的这两个“上下文”对象。

LocalStack

在介绍“请求上下文”和“应用上下文”之前,我们对LocalStack简要做一个回顾。在Werkzeug库——local模块一文中,我们讲解了werkzeug.local模块中实现的三个类LocalLocalStackLocalProxy。关于它们的概念和详细介绍,可以查看上面的文章。这里,我们用一个例子来说明Flask中使用的一种数据结构LocalStack

>>> from werkzeug.local import LocalStack
>>> import threading# 创建一个`LocalStack`对象
>>> local_stack = LocalStack()
# 查看local_stack中存储的信息
>>> local_stack._local.__storage__
{}# 定义一个函数,这个函数可以向`LocalStack`中添加数据
>>> def worker(i):local_stack.push(i)# 使用3个线程运行函数`worker`
>>> for i in range(3):t = threading.Thread(target=worker, args=(i,))t.start()# 再次查看local_stack中存储的信息
>>> local_stack._local.__storage__
{<greenlet.greenlet at 0x4bee5a0>: {'stack': [2]},<greenlet.greenlet at 0x4bee638>: {'stack': [1]},<greenlet.greenlet at 0x4bee6d0>: {'stack': [0]}
}

由上面的例子可以看出,存储在LocalStack中的信息以字典的形式存在:键为线程/协程的标识数值,值也是字典形式。每当有一个线程/协程上要将一个对象pushLocalStack栈中,会形成如上一个“键-值”对。这样的一种结构很好地实现了线程/协程的隔离,每个线程/协程都会根据自己线程/协程的标识数值确定存储在栈结构中的值。

LocalStack还实现了pushpoptop等方法。其中top方法永远指向栈顶的元素。栈顶的元素是指当前线程/协程中最后被推入栈中的元素,即local_stack._local.stack[-1](注意,是stack键对应的对象中最后被推入的元素)。

请求上下文

Flask中所有的请求处理都在“请求上下文”中进行,在它设计之初便就有这个概念。由于0.9版本代码比较复杂,这里还是以0.1版本的代码为例进行说明。本质上这两个版本的“请求上下文”的运行原理没有变化,只是新版本增加了一些功能,这点在后面再进行解释。

请求上下文——0.1版本

# Flask v0.1
class _RequestContext(object):"""The request context contains all request relevant information.  It iscreated at the beginning of the request and pushed to the`_request_ctx_stack` and removed at the end of it.  It will create theURL adapter and request object for the WSGI environment provided."""def __init__(self, app, environ):self.app = appself.url_adapter = app.url_map.bind_to_environ(environ)self.request = app.request_class(environ)self.session = app.open_session(self.request)self.g = _RequestGlobals()self.flashes = Nonedef __enter__(self):_request_ctx_stack.push(self)def __exit__(self, exc_type, exc_value, tb):# do not pop the request stack if we are in debug mode and an# exception happened.  This will allow the debugger to still# access the request object in the interactive shell.if tb is None or not self.app.debug:_request_ctx_stack.pop()

由上面“请求上下文”的实现可知:

  • “请求上下文”是一个上下文对象,实现了__enter____exit__方法。可以使用with语句构造一个上下文环境。

  • 进入上下文环境时,_request_ctx_stack这个栈中会推入一个_RequestContext对象。这个栈结构就是上面讲的LocalStack栈。

  • 推入栈中的_RequestContext对象有一些属性,包含了请求的的所有相关信息。例如apprequestsessiongflashes。还有一个url_adapter,这个对象可以进行URL匹配。

  • with语句构造的上下文环境中可以进行请求处理。当退出上下文环境时,_request_ctx_stack这个栈会销毁刚才存储的上下文对象。

以上的运行逻辑使得请求的处理始终在一个上下文环境中,这保证了请求处理过程不被干扰,而且请求上下文对象保存在LocalStack栈中,也很好地实现了线程/协程的隔离。

以下是一个简单的例子:

# example - Flask v0.1
>>> from flask import Flask, _request_ctx_stack
>>> import threading
>>> app = Flask(__name__)
# 先观察_request_ctx_stack中包含的信息
>>> _request_ctx_stack._local.__storage__
{}# 创建一个函数,用于向栈中推入请求上下文
# 本例中不使用`with`语句
>>> def worker():# 使用应用的test_request_context()方法创建请求上下文request_context = app.test_request_context()_request_ctx_stack.push(request_context)# 创建3个进程分别执行worker方法
>>> for i in range(3):t = threading.Thread(target=worker)t.start()# 再观察_request_ctx_stack中包含的信息
>>> _request_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x5e45df0>: {'stack': [<flask._RequestContext at 0x710c668>]},<greenlet.greenlet at 0x5e45e88>: {'stack': [<flask._RequestContext at 0x7107f28>]},<greenlet.greenlet at 0x5e45f20>: {'stack': [<flask._RequestContext at 0x71077f0>]}
}

上面的结果显示:_request_ctx_stack中为每一个线程创建了一个“键-值”对,每一“键-值”对中包含一个请求上下文对象。如果使用with语句,在离开上下文环境时栈中销毁存储的上下文对象信息。

请求上下文——0.9版本

在0.9版本中,Flask引入了“应用上下文”的概念,这对“请求上下文”的实现有一定的改变。这个版本的“请求上下文”也是一个上下文对象。在使用with语句进入上下文环境后,_request_ctx_stack会存储这个上下文对象。不过与0.1版本相比,有以下几点改变:

  • 请求上下文实现了pushpop方法,这使得对于请求上下文的操作更加的灵活;

  • 伴随着请求上下文对象的生成并存储在栈结构中,Flask还会生成一个“应用上下文”对象,而且“应用上下文”对象也会存储在另一个栈结构中去。这是两个版本最大的不同。

我们先看一下0.9版本相关的代码:

# Flask v0.9
def push(self):"""Binds the request context to the current context."""top = _request_ctx_stack.topif top is not None and top.preserved:top.pop()# Before we push the request context we have to ensure that there# is an application context.app_ctx = _app_ctx_stack.topif 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)self.session = self.app.open_session(self.request)if self.session is None:self.session = self.app.make_null_session()

我们注意到,0.9版本的“请求上下文”的pop方法中,当要将一个“请求上下文”推入_request_ctx_stack栈中的时候,会先检查另一个栈_app_ctx_stack的栈顶是否存在“应用上下文”对象或者栈顶的“应用上下文”对象的应用是否是当前应用。如果不存在或者不是当前对象,Flask会自动先生成一个“应用上下文”对象,并将其推入_app_ctx_stack中。

我们再看离开上下文时的相关代码:

# Flask v0.9
def pop(self, exc=None):"""Pops the request context and unbinds it by doing that.  This willalso trigger the execution of functions registered by the:meth:`~flask.Flask.teardown_request` decorator... versionchanged:: 0.9Added the `exc` argument."""app_ctx = self._implicit_app_ctx_stack.pop()clear_request = Falseif not self._implicit_app_ctx_stack:self.preserved = Falseif exc is None:exc = sys.exc_info()[1]self.app.do_teardown_request(exc)clear_request = Truerv = _request_ctx_stack.pop()assert rv is self, 'Popped wrong request context.  (%r instead of %r)' \% (rv, self)# get rid of circular dependencies at the end of the request# so that we don't require the GC to be active.if clear_request:rv.request.environ['werkzeug.request'] = None# Get rid of the app as well if necessary.if app_ctx is not None:app_ctx.pop(exc)

上面代码中的细节先不讨论。注意到当要离开以上“请求上下文”环境的时候,Flask会先将“请求上下文”对象从_request_ctx_stack栈中销毁,之后会根据实际的情况确定销毁“应用上下文”对象。

以下还是以一个简单的例子进行说明:

# example - Flask v0.9
>>> from flask import Flask, _request_ctx_stack, _app_ctx_stack
>>> app = Flask(__name__)# 先检查两个栈的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{}# 生成一个请求上下文对象
>>> request_context = app.test_request_context()
>>> request_context.push()# 请求上下文推入栈后,再次查看两个栈的内容
>>> _request_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x6eb32a8>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
>>> _app_ctx_stack._local.__storage__
{<greenlet.greenlet at 0x6eb32a8>: {'stack': [<flask.ctx.AppContext at 0x5c96a58>]}}>>> request_context.pop()# 销毁请求上下文时,再次查看两个栈的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{}

应用上下文

上部分中简单介绍了“应用上下文”和“请求上下文”的关系。那什么是“应用上下文”呢?我们先看一下它的类:

class AppContext(object):"""The application context binds an application object implicitlyto the current thread or greenlet, similar to how the:class:`RequestContext` binds request information.  The applicationcontext is also implicitly created if a request context is createdbut the application is not on top of the individual applicationcontext."""def __init__(self, app):self.app = appself.url_adapter = app.create_url_adapter(None)# Like request context, app contexts can be pushed multiple times# but there a basic "refcount" is enough to track them.self._refcnt = 0def push(self):"""Binds the app context to the current context."""self._refcnt += 1_app_ctx_stack.push(self)def pop(self, exc=None):"""Pops the app context."""self._refcnt -= 1if self._refcnt <= 0:if exc is None:exc = sys.exc_info()[1]self.app.do_teardown_appcontext(exc)rv = _app_ctx_stack.pop()assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \% (rv, self)def __enter__(self):self.push()return selfdef __exit__(self, exc_type, exc_value, tb):self.pop(exc_value)

由以上代码可以看出:“应用上下文”也是一个上下文对象,可以使用with语句构造一个上下文环境,它也实现了pushpop等方法。“应用上下文”的构造函数也和“请求上下文”类似,都有appurl_adapter等属性。“应用上下文”存在的一个主要功能就是确定请求所在的应用。

然而,以上的论述却又让人产生这样的疑问:既然“请求上下文”中也包含app等和当前应用相关的信息,那么只要调用_request_ctx_stack.top.app或者魔法current_app就可以确定请求所在的应用了,那为什么还需要“应用上下文”对象呢?对于单应用单请求来说,使用“请求上下文”确实就可以了。然而,Flask的设计理念之一就是多应用的支持。当在一个应用的请求上下文环境中,需要嵌套处理另一个应用的相关操作时,“请求上下文”显然就不能很好地解决问题了。如何让请求找到“正确”的应用呢?我们可能会想到,可以再增加一个请求上下文环境,并将其推入_request_ctx_stack栈中。由于两个上下文环境的运行是独立的,不会相互干扰,所以通过调用_request_ctx_stack.top.app或者魔法current_app也可以获得当前上下文环境正在处理哪个应用。这种办法在一定程度上可行,但是如果对于第二个应用的处理不涉及到相关请求,那也就无从谈起“请求上下文”。

为了应对这个问题,Flask中将应用相关的信息单独拿出来,形成一个“应用上下文”对象。这个对象可以和“请求上下文”一起使用,也可以单独拿出来使用。不过有一点需要注意的是:在创建“请求上下文”时一定要创建一个“应用上下文”对象。有了“应用上下文”对象,便可以很容易地确定当前处理哪个应用,这就是魔法current_app。在0.1版本中,current_app是对_request_ctx_stack.top.app的引用,而在0.9版本中current_app是对_app_ctx_stack.top.app的引用。

下面以一个多应用的例子进行说明:

# example - Flask v0.9
>>> from flask import Flask, _request_ctx_stack, _app_ctx_stack
# 创建两个Flask应用
>>> app = Flask(__name__)
>>> app2 = Flask(__name__)
# 先查看两个栈中的内容
>>> _request_ctx_stack._local.__storage__
{}
>>> _app_ctx_stack._local.__storage__
{}
# 构建一个app的请求上下文环境,在这个环境中运行app2的相关操作
>>> with app.test_request_context():print "Enter app's Request Context:"print _request_ctx_stack._local.__storage__print _app_ctx_stack._local.__storage__printwith app2.app_context():print "Enter app2's App Context:"print _request_ctx_stack._local.__storage__print _app_ctx_stack._local.__storage__print# do somethingprint "Exit app2's App Context:"print _request_ctx_stack._local.__storage__print _app_ctx_stack._local.__storage__print
# Result
Enter app's Request Context:
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>]}}Enter app2's App Context:
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>, <flask.ctx.AppContext object at 0x0000000007313198>]}}Exit app2's App Context
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<RequestContext 'http://localhost/' [GET] of __main__>]}}
{<greenlet.greenlet object at 0x000000000727A178>: {'stack': [<flask.ctx.AppContext object at 0x0000000005DD0DD8>]}}

在以上的例子中:

  • 我们首先创建了两个Flask应用appapp2

  • 接着我们构建了一个app的请求上下文环境。当进入这个环境中时,这时查看两个栈的内容,发现两个栈中已经有了当前请求的请求上下文对象和应用上下文对象。并且栈顶的元素都是app的请求上下文和应用上下文;

  • 之后,我们再在这个环境中嵌套app2的应用上下文。当进入app2的应用上下文环境时,两个上下文环境便隔离开来,此时再查看两个栈的内容,发现_app_ctx_stack中推入了app2的应用上下文对象,并且栈顶指向它。这时在app2的应用上下文环境中,current_app便会一直指向app2

  • 当离开app2的应用上下文环境,_app_ctx_stack栈便会销毁app2的应用上下文对象。这时查看两个栈的内容,发现两个栈中只有app的请求的请求上下文对象和应用上下文对象。

  • 最后,离开app的请求上下文环境后,两个栈便会销毁app的请求的请求上下文对象和应用上下文对象,栈为空。

与上下文对象有关的“全局变量”

在Flask中,为了更加方便地处理一些变量,特地提出了“全局变量”的概念。这些全局变量有:

# Flask v0.9
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_object, 'request'))
session = LocalProxy(partial(_lookup_object, 'session'))
g = LocalProxy(partial(_lookup_object, 'g'))# 辅助函数
def _lookup_object(name):top = _request_ctx_stack.topif top is None:raise RuntimeError('working outside of request context')return getattr(top, name)def _find_app():top = _app_ctx_stack.topif top is None:raise RuntimeError('working outside of application context')return top.app

可以看出,Flask中使用的一些“全局变量”,包括current_apprequestsessiong等都来自于上下文对象。其中current_app一直指向_app_ctx_stack栈顶的“应用上下文”对象,是对当前应用的引用。而requestsessiong等一直指向_request_ctx_stack栈顶的“请求上下文”对象,分别引用请求上下文的requestsessiong。不过,从 Flask 0.10 起,对象 g 存储在应用上下文中而不再是请求上下文中。

另外一个问题,在形成这些“全局变量”的时候,使用了werkzeug.local模块的LocalProxy类。之所以要用该类,主要是为了动态地实现对栈顶元素的引用。如果不使用这个类,在生成上述“全局变量”的时候,它们因为指向栈顶元素,而栈顶元素此时为None,所以这些变量也会被设置为None常量。后续即使有上下文对象被推入栈中,相应的“全局变量”也不会发生改变。为了动态地实现对栈顶元素的引用,这里必须使用werkzeug.local模块的LocalProxy类。

Flask中的请求上下文和应用上下文相关推荐

  1. Flask框架(flask中的请求上下文和应用上下文,以及请求钩子的使用,Flask-Script 扩展命令行)

    1.请求上下文与应用上下文 请求上下文(request context) request和session都属于请求上下文对象. 应用上下文(application context) current_a ...

  2. flask中的请求上下文

    2019独角兽企业重金招聘Python工程师标准>>> 根据前一篇应用上下文可知,请求上下文的作用域是发生在请求到来之后. 如有一个应用函数返回用户应该跳转到的URL.想象它总是会跳 ...

  3. Flask04——请求上下文和应用上下文

    在flask框架中,引入了上下文的概念,为了能够让大家真实的感受到上下文在flask框架中所起到的作用,所以下面我就用一个具体的需求实例来给大家进行说明,首先在我不使用上下文思路的情况下来解决这一需求 ...

  4. Web框架——Flask系列之请求上下文与应用上下文请求钩子Flask-Script扩展命令行(十七)

    一.请求上下文和应用上下文 请求上下文(request context) request和session都属于请求上下文对象. 应用上下文(application context) current_a ...

  5. Flask中的上下文

    1. 分为请求上下文和应用上下文 应用上下文和请求上下文都是存放到一个LocalStack的栈中 应用app相关的操作就必须要用到应用上下文 请求相关的操作就必须用到请求上下文 2.请求上下文中还包括 ...

  6. Flask 中的上下文管理和请求钩子

    Flask 中的上下文管理和请求钩子 在使用 Flask 框架实现功能接口的时候,前端点击按钮发送请求的请求方式和 form 表单提交给后端的数据,后端都是通过 Flask 中的 request 对象 ...

  7. Flask中current_app和g对象

    Flask中有两种上下文,请求上下文和应用上下文. 请求上下文(request context) request和session都属于请求上下文对象. request:封装了HTTP请求的内容,针对的 ...

  8. 进程上下文和中断上下文

    1.进程上下文 进程上下文实际上是进程执行活动全过程的静态描述.我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为上文,把正在执行的指令和数据在寄存器和堆栈中的内容称为正文,把待执行的指令和 ...

  9. flask中的request

    1.request是什么? 简单来说,它就是flask的封装的一个对象,这个对象包含着前端请求所带的所有信息.既然说它是一个对象,那么它肯定是有一些熟悉,和方法的,下面就来介绍下request里的熟悉 ...

最新文章

  1. 前端领域的BEM到底是什么
  2. Storm入门(一)原理介绍
  3. 用计算机求锐角A,B,计算机操作题
  4. Java并发编程的艺术(十)——Java中的锁(5)
  5. mysql 序列_MySql中序列的应用和总结
  6. Redmine Gantt 实现 (Show relations in Gantt diagram)
  7. 今晚直播丨Oracle数据库之Object的Access方法和结合方法
  8. 罗永浩要造智能音箱;苹果承认bug;微软特制AI曝光 | 极客头条
  9. Microsoft Project 2010
  10. 查看unix和linux的HBA卡的WWN地址
  11. 交通灯控制系统的设计
  12. win10系统从旧固态迁移到新固态,win10系统迁移到固态硬盘ssd
  13. Relay和Rendezvous
  14. 用ChatGPT可以去微博做个大V了(狗头)
  15. 让脂肪起内讧?从内部全面瓦解脂肪
  16. MathType中的四则运算符号该怎样进行输入
  17. 贴片电容的封装尺寸容值解读
  18. echarts 饼图 逆时针降序排序
  19. mongodb的学习之旅一
  20. 使用canvas画直线箭头

热门文章

  1. Java集合框架(一)
  2. C语言及程序设计初步例程-37 循环嵌套
  3. 【收藏】13个CSS3快速必备开发工具
  4. nstruts2.0发布前奏---浅谈struts和依赖注入在项目中的应用
  5. java内存系列:测试JDK最大内存
  6. laravel大型项目系列教程(四)之显示文章列表和用户修改文章
  7. ibm服务器hyper-v搭建的虚拟机操作系统数据如何导出到本机,将虚拟机从Hyper-V转移到KVM|导出完整vhdx磁盘转换qcow2...
  8. xtrbackup更换数据库_XtraBackup 备份还原 MySQL 数据库
  9. android 如何判断h5页面是否加载完成_H5 键盘兼容性小结
  10. 荧光透视的计算机辅助外科手术,「电信学」「2008.11」基于荧光透视的电磁跟踪骨科X射线导航实践研究...