概述

在使用 Flask 的过程中,有一项很舒服的功能不知道小伙伴们有没有发现,那么就是我们写 View 的时候居然不用传递 requestresponse 变量,例如直接这些写就好了:

图 1:flask 使用示例

但是,用过 Django 和 Tornado 的同学也都知道,在 Django 中是需要我们自己传递 request 对象的,就像这样:

图 2:request 对象

Tornado 里面虽然没有传递这样的 Request 变量,但是,Tornado 却是需要集成自 RequestHandler 类,其实也就是变相得传递了。

那么为什么都是 Web 框架,而 Flask 却可以做到不需要传递 request 呢?这其实就是这篇文章要解析得 Context 上下文。而且这个 Context 不仅影响 request, 还会影响全局变量 g 和一些参数。

什么是上下文

从 Flask 的官方文档中,我们可以发现在 Flask 中,Context 分为两类,分别是 AppContextRequestContext。其中 RequestCntext 是只针对于单次请求有效的,也就是我们常用的 requestsession 这类;另外一种 AppContext 是针对于整个应用周期的,可以理解为 Flask 的 Web 应用一启动就有,关闭就结束的一种 Context,例如我们常用的 gcurrent_app 变量这一类。

Context 如何起作用

为了理解 Context 是如何起作用的,我们还是来跟踪一下源代码看看,打开 flask/globals.py 这个文件,可以看到以下的内容:

flask/globals.py

图 3:Context 实现

这里可以发现,所有的这些变量都跟 Stack 有关,而Stack 也分为两种,分别是:

  1. line 56: _request_ctx_stack = LocalStack()
  2. line 57: _app_ctx_stack = LocalStack()

何时入栈 出栈

flask/app.py

  1. line 1936: def wsgi_app(self, environ, start_response):
  2. ... ...
  3. line 1961: ctx = self.request_context(environ)
  4. line 1961: ctx.push()
  5. line 1961: error = None
  6. line 1961: try:
  7. line 1961: try:
  8. line 1961: response = self.full_dispatch_request()
  9. line 1961: except Exception as e:
  10. line 1961: error = e
  11. line 1961: response = self.make_response(self.handle_exception(e))
  12. line 1961: return response(environ, start_response)
  13. line 1961: finally:
  14. line 1961: if self.should_ignore_error(error):
  15. line 1961: error = None
  16. line 1961: ctx.auto_pop(error)

这里可以发现其实是每次请求过来的时候就入栈了,然后请求完成之后就出栈了。

有没有可能栈里面有多个元素

栈里面不会出现多个元素,但是,在调试模式下,可能在一个请求出错之后元素不会出栈,代码在:

flask/ctx.py

  1. line 377: def auto_pop(self, exc):
  2. line 378: if self.request.environ.get('flask._preserve_context') or \
  3. line 379: (exc is not None and self.app.preserve_context_on_exception):
  4. line 380: self.preserved = True
  5. line 381: self._preserved_exc = exc
  6. line 382: else:
  7. line 383: self.pop(exc)

然后在下一次的请求入栈前先出栈

  1. line 297: def push(self):
  2. ... ...
  3. line 307: top = _request_ctx_stack.top
  4. line 308: if top is not None and top.preserved:
  5. line 309: top.pop(top._preserved_exc)

遗留问题:如果不会出现多个元素干嘛要用栈?

Context 里面有什么内容