在Flask中处理请求时,应用会生成一个“请求上下文”对象。整个请求的处理过程,都会在这个上下文对象中进行。这保证了请求的处理过程不被干扰。处理请求的具体代码如下:
创新互联建站专业提供成都移动机房服务,为用户提供五星数据中心、电信、双线接入解决方案,用户可自行在线购买成都移动机房服务,并享受7*24小时金牌售后服务。
- 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用来存储“请求上下文”对象。这在《一个Flask应用运行过程剖析 》中有所介绍。在0.9版本中,Flask又引入了“应用上下文”的概念。本文主要Flask中的这两个“上下文”对象。
LocalStack
在介绍“请求上下文”和“应用上下文”之前,我们对LocalStack简要做一个回顾。在Werkzeug库——local模块一文中,我们讲解了werkzeug.local模块中实现的三个类Local、LocalStack和LocalProxy。关于它们的概念和详细介绍,可以查看上面的文章。这里,我们用一个例子来说明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__
- {
: {'stack': [2]}, : {'stack': [1]}, : {'stack': [0]} - }
由上面的例子可以看出,存储在LocalStack中的信息以字典的形式存在:键为线程/协程的标识数值,值也是字典形式。每当有一个线程/协程上要将一个对象push进LocalStack栈中,会形成如上一个“键-值”对。这样的一种结构很好地实现了线程/协程的隔离,每个线程/协程都会根据自己线程/协程的标识数值确定存储在栈结构中的值。
LocalStack还实现了push、pop、top等方法。其中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 is
- created at the beginning of the request and pushed to the
- `_request_ctx_stack` and removed at the end of it. It will create the
- URL adapter and request object for the WSGI environment provided.
- """
- def __init__(self, app, environ):
- self.app = app
- self.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 = None
- def __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()
由上面“请求上下文”的实现可知:
以下是一个简单的例子:
- # 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__
- {
: {'stack': [ ]}, : {'stack': [ ]}, : {'stack': [ ]} - }
上面的结果显示:_request_ctx_stack中为每一个线程创建了一个“键-值”对,每一“键-值”对中包含一个请求上下文对象。如果使用with语句,在离开上下文环境时栈中销毁存储的上下文对象信息。
请求上下文——0.9版本
在0.9版本中,Flask引入了“应用上下文”的概念,这对“请求上下文”的实现有一定的改变。这个版本的“请求上下文”也是一个上下文对象。在使用with语句进入上下文环境后,_request_ctx_stack会存储这个上下文对象。不过与0.1版本相比,有以下几点改变:
我们先看一下0.9版本相关的代码:
- # Flask v0.9
- def push(self):
- """Binds the request context to the current context."""
- top = _request_ctx_stack.top
- if 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.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)
- 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 will
- also trigger the execution of functions registered by the
- :meth:`~flask.Flask.teardown_request` decorator.
- .. versionchanged:: 0.9
- Added the `exc` argument.
- """
- app_ctx = self._implicit_app_ctx_stack.pop()
- clear_request = False
- if not self._implicit_app_ctx_stack:
- self.preserved = False
- if exc is None:
- exc = sys.exc_info()[1]
- self.app.do_teardown_request(exc)
- clear_request = True
- rv = _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__
- {
: {'stack': [ ]}} - >>> _app_ctx_stack._local.__storage__
- {
: {'stack': [ ]}} - >>> request_context.pop()
- # 销毁请求上下文时,再次查看两个栈的内容
- >>> _request_ctx_stack._local.__storage__
- {}
- >>> _app_ctx_stack._local.__storage__
- {}
应用上下文
上部分中简单介绍了“应用上下文”和“请求上下文”的关系。那什么是“应用上下文”呢?我们先看一下它的类:
- class AppContext(object):
- """The application context binds an application object implicitly
- to the current thread or greenlet, similar to how the
- :class:`RequestContext` binds request information. The application
- context is also implicitly created if a request context is created
- but the application is not on top of the individual application
- context.
- """
- def __init__(self, app):
- self.app = app
- self.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 = 0
- def 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 -= 1
- if 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):
由以上代码可以看出:“应用上下文”也是一个上下文对象,可以使用with语句构造一个上下文环境,它也实现了push、pop等方法。“应用上下文”的构造函数也和“请求上下文”类似,都有app、url_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__
- with app2.app_context():
- print "Enter app2's App Context:"
- print _request_ctx_stack._local.__storage__
- print _app_ctx_stack._local.__storage__
- # do something
- print "Exit app2's App Context:"
- print _request_ctx_stack._local.__storage__
- print _app_ctx_stack._local.__storage__
- # Result
- Enter app's Request Context:
- {
: {'stack': [ ]}} - {
: {'stack': [ ]}} - Enter app2's App Context:
- {
: {'stack': [ ]}} - {
: {'stack': [ , ]}} - Exit app2's App Context
- {
: {'stack': [ ]}} - {
: {'stack': [ ]}}
在以上的例子中:
与上下文对象有关的“全局变量”
在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.top
- if top is None:
- raise RuntimeError('working outside of request 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
可以看出,Flask中使用的一些“全局变量”,包括current_app、request、session、g等都来自于上下文对象。其中current_app一直指向_app_ctx_stack栈顶的“应用上下文”对象,是对当前应用的引用。而request、session、g等一直指向_request_ctx_stack栈顶的“请求上下文”对象,分别引用请求上下文的request、session和g。不过,从 Flask 0.10 起,对象 g 存储在应用上下文中而不再是请求上下文中。
另外一个问题,在形成这些“全局变量”的时候,使用了werkzeug.local模块的LocalProxy类。之所以要用该类,主要是为了动态地实现对栈顶元素的引用。如果不使用这个类,在生成上述“全局变量”的时候,它们因为指向栈顶元素,而栈顶元素此时为None,所以这些变量也会被设置为None常量。后续即使有上下文对象被推入栈中,相应的“全局变量”也不会发生改变。为了动态地实现对栈顶元素的引用,这里必须使用werkzeug.local模块的LocalProxy类。
名称栏目:Flask中的请求上下文和应用上下文
网站URL:http://www.shufengxianlan.com/qtweb/news28/485478.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联