Flask上下文对象

网上很多针对flask上下文的文章,本文为我对自己学习flask,做项目之余,记下的笔记。

在Flask中有两个上下文对象:

  • 应用上下文
  • 请求上下文

这两个对象,其实本质是对核心对象Flask和Request的封装。

应用上下文:AppContext:

flask源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
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 = 0

应用上下文对象init函数,第一句就对app进行封装到这个AppContext对象中,最终这个对象不仅仅包含了核心对象app,还包含了外部的一些数据信息。

请求上下文:RequestContext:针对Request进行封装。一个请求的所有信息都可以在这个对象中获取到。

源代码:

1
2
3
4
5
6
7
8
9
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 = None

如上,同样首先就对核心对象app封装,作为RequestContext对象的属性保存了起来。

另外,以上两个对象还存在类似的pop,push,enter,exit等关键方法,从名字可以联想到栈这种数据结构。接着看源码:
在globals中:

1
2
3
4
5
6
7
8
9
10
# context locals
_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'))

这里有好几个关键且很晦涩的实例,从网上找到了慕课网大神整理的流程图在下:
流程图

当一个请求进入,则首先实例化一个请求上下文,并且检查_app_ctx_stack栈顶元素是否为空,如果为空则将AppContext推出栈中,再将此RequestContext上下文推入LocalStack栈中,并且_request_ctx_stack始终指向这个栈的栈顶。
两个栈的栈顶都是上下文对象。并且,current_app和request都指向栈顶,且为代理对象。如果栈顶没有值,则调用current_app或者request则会报错unbound.

所以,当我们有时候需要使用current_app的时候,需要明白上下文是否在栈中。

当请求从浏览器发过来的时候,请求上下文推入栈中之前,自动将应用上下文推入了栈中。于是,我们可以调用current_app。
而有时候在写test代码到时候,是没有请求发过来,flask自动将应用上下文入栈的。于是,需要我们手动执行入栈操作。

并且,上下文管理器则可以使用with语句进行编码,简化了我们的操作。

再看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)

# context locals
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app

_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'))

其中关键的两句代码,current_app是Flask核心对象(top.app),request得到的Request请求对象。这个对象,不是上下文对象。

回到上文,请求结束之后,这个请求对象会出栈。