Flask中的上下文的概念是Flask中非常精妙的一个设计, 通过上下文将请求和应用中需要的内容, 封装起来并且使得内容做到线程隔离. 理解这个机制首先从本地线程讲起.

本地线程

import threading

mydata = threading.local()
mydata.number = 42
print(mydata.number)

log = []

def f():
    mydata.number = 11
    log.append(mydata.number)

thread = threading.Thread(target=f)
thread.start()
thread.join()
print(log)
print(mydata.number)

输出:

(flask) ➜  pyWeb python ch3_local.py
42
[11]
42

本地线程: 不同线程对内容的修改只在线程内部发挥作用. 子线程和父线程中的变量不会相互影响, 原理是:在threading.current_thread().__dict__里添加一个包含对象mydata的id值的key来保存不同线程的状态

Wekzeug中的local

Wekzeug对本地线程进行了自己的实现, 相比threading.local有以下区别:

  • 使用__storage__保存不同线程的状态
  • 提供release_local方法释放本地线程的变量.
  • Wekzeug实现了两种与本地线程相关的数据结构
  • LocalStack: 基于werkzeug.local.Local实现的栈结构
  • LocalProxy: 构造此结构时接受一个可以调用的参数(通常是函数), 这个函数执行后就是通过LocalStack实例化的栈的栈顶对象. 对于LocalProxy对象的操作实际上都会转发到这个栈顶对象.

flask.request

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
	name = request.args.get('name')

以上例子中 name变量引用了request但是这时候并没有args对象, 直到有请求进来时, 才有name内容. 事实上, request就是一个localProxy, 上述代码的实际过程是:

  • 用户访问发生请求
  • 发生请求的过程中向`_request_ctx_stack推入这个请求上下文的对象, 它会变成栈顶. request就会指向这个请求上下文.
  • 在视图函数中使用request就能够使用上下文中的args.get('name')了.

这样做的好处是: 首先线程独立, 第二, 通过代理把一些局部对象变成全局可访问.

使用上下文

应用上下文和请求上下文的关系

应用上下文缓存发生在请求之前要使用到的资源, 请求上下文通常储存本次请求包含的上下文, 例如请求包含的参数和数据. 请求上下文发生在HTTP请求开始, WSGI Server调用Flask.__call__()之后. 两者之间的关系:

class RequestContext(object):
	self._implicit_app_ctx_stack = []
def push(self):
	# some stuff
	app_ctx = _app_ctx_stack.top
	if app_ctx is None or app_ctx != self.app:
		app_ctx = self.app.app_context()
		app_ctx.push()
		self._implicit_app_ctx_stack.append(app_ctx)
	# some other stuff
def pop(self, exc = _sentinel):
	app_ctx = self._implicit_ctx_stack.pop()
	try:
		# some stuff	
	finally:
		# some other stuff
		if app_ctx is not None:
			app_ctx.pop(exc)

在应用环境中, 请求上下文和应用上下文是一一对应的, 请求上下文和应用上下文都是本地线程.

Flask中的上下文变量

  • flask.current_app: 应用上下文, 当前app实例对象
  • flask.g: 应用上下文. 处理请求时用作临时存储的对象
  • flask.request: 请求上下文. 封装了客户端发出的HTTP请求中的内容
  • flask.session: 请求上下文: 存储了用户会话.

为什么要区分应用上下文和请求上下文

  • 使用DispatcherMiddleware, 支持多个app共存.
  • 非Web模式下. 一个应用上下文可以对应多个请求上下文.