bigywater.tech
Flask · 5

current_app、request、g 的实现

request 明明是个全局名字,为什么每个请求读到的都是自己的?它的类型到底是什么?这篇把代理对象拆开,看它背后的 ContextVar,并测它的开销。

LocalProxy
request、current_app、g、session 的类型
9.8×
通过代理访问属性相对直接访问的耗时倍数
4 / 4
4 个线程各自读到不同的 request.path

代理对象

实测 · 本机执行输出
  request      type = LocalProxy  真实对象类型 = Request
  current_app  type = LocalProxy  真实对象类型 = Flask
  g            type = LocalProxy  真实对象类型 = _AppCtxGlobals
  session      type = LocalProxy  真实对象类型 = SecureCookieSession
  isinstance(request, Request) = True | request.__class__ = Request | type(request) is Request = False
  request is request._get_current_object(): False | request == request._get_current_object(): True
  repr(request) = <Request 'http://localhost/p?x=1' [GET]>
  请求之外 repr(request) = <LocalProxy unbound>

上一篇:Flask Request Context;下一篇:Context Local 与线程隔离。

LocalProxy 与 ContextVar

实测 · 本机执行输出
  flask.globals._cv_request: <ContextVar name='flask.request_ctx' at 0x10acf2f20> | 类型 ContextVar
  werkzeug.local.LocalProxy.__init__ 里处理 ContextVar 的分支(原样):
            elif isinstance(local, ContextVar):
    
                def _get_current_object() -> T:
                    try:
                        obj = local.get()
                    except LookupError:
                        raise RuntimeError(unbound_message) from None
    
                    return get_name(obj)

线程隔离

实测 · 本机执行输出
  线程 0:request.path = /t0,真实 Request 对象 id = 16880
  线程 1:request.path = /t1,真实 Request 对象 id = 18480
  线程 2:request.path = /t2,真实 Request 对象 id = 66816
  线程 3:request.path = /t3,真实 Request 对象 id = 68640
  4 个线程看到的路径互不相同: True | 对象 id 互不相同: True

访问开销

实测 · 本机执行输出
  通过代理 request.path:242 ns/次;真实对象 .path:25 ns/次;约 9.8 倍

g 对象

实测 · 本机执行输出
  g.get("b", "默认") = 默认 | "a" in g = True | g.setdefault("c", 3) = 3 | g.pop("a") = 1 | list(g) = ['c']
  g.missing → AttributeError: missing
  repr(g) = <flask.g of 'demo5'>
  自定义 app_ctx_globals_class 后 g.created = True | 类型 = MyG

传给线程

实测 · 本机执行输出
  传代理: RuntimeError: Working outside of application context.

This typically means that you
  传真实对象: demo5

分步演示

LocalProxy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动(werkzeug.local.LocalProxy 的源码取自本机安装的 Werkzeug 3.1.9)
    • 没有验证:greenlet/gevent 下代理的行为(见第 142 篇)、asyncio 任务里上下文的复制语义、Flask 旧版本里基于 LocalStack 的实现、代理对象在 pickle 或序列化时的表现
    京ICP备2026067502号-1京公网安备11010502063487号