浅析Flask中的g和request

本人flask小白一枚,在刚开始使用flask时就比较纳闷g和request有什么区别,问同事也只是含糊地回答了一个是应用上下文使用的,另一个是请求上下文使用的,但具体他们是如何生成的,有何不同还是比较模糊。抽时间看了一个flask源码,总结一下自己的理解。

从g、request的定义说起

查看Flask的源码可知,g和request定于在globlas.py文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)
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'))

从上面的代码可以看出,g是从当前应用上下栈中取出的,那么g是什么时候赋值的呢?

AppContext和RequestContext

先来看 RequestContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class RequestContext(object):
...
def _get_g(self):
return _app_ctx_stack.top.g
def _set_g(self, value):
_app_ctx_stack.top.g = value
g = property(_get_g, _set_g)
del _get_g, _set_g
...
def push(self):
"""Binds the request context to the current context."""
# If an exception occurs in debug mode or if context preservation is
# activated under exception situations exactly one context stays
# on the stack. The rationale is that you want to access that
# information under debug situations. However if someone forgets to
# pop that context again we want to make sure that on the next push
# it's invalidated, otherwise we run at risk that something leaks
# memory. This is usually only a problem in test suite since this
# functionality is not active in production environments.
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)
...
def __enter__(self):
self.push()
return self

从上面的代码来看,当请求上下文创建时,会执行压栈操作,如果发现当前应用上下文不存在或不是当前应用,将创建一个新的应用上下文并压栈。
再看看AppContext的代码:

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

很清楚了,g就是在此时创建的。
也就是说,一个请求过来,Flask先创建RequestContext,RequestContext会检查是否存在AppContext,若存在且是同一个App,只将requestcontext压入_request_ctx_stack.
如果不存在AppContext则创建一个AppContext(就是此时创建的g),并压入_app_ctx_stack,然后再将requestcontext压入_request_ctx_stack.

LocalStack和LocalProxy

LocalStack和LocalProxy都是werkzeug里的概念,目的是为了保证每个请求过来都是协程隔离的。
关于LocalStack和LocalProxy这里的两篇文章讲的很清楚:Werkzeug(Flask)之Local、LocalStack和LocalProxy
Flask 的 Context 机制

总结

总而言之,g和request在你只有一个应用的时候分的并不是那么清楚,比如我既可以用request.args来获取请求的参数也可以使用g.args。g保存的是应用层面的全局变量,request保存的是请求上下文的变量。