用 Flask 3.1.3 验证 Flask(__name__) 构造时做了什么:注册 /static 路由、29 个默认配置键、默认上下文处理器,jinja_env 与 logger 惰性创建;root_path 随模块位置变化;构造一次中位数 206 µs。
用真实进程验证 app.run():启动信息、只监听 127.0.0.1、debug=True 多出重载器子进程(改文件约 0.64 秒重载)、threaded=True 时 2 个并发请求总耗时 511 ms 对 1018 ms、flask run 不执行 __main__ 里的代码。
验证 Flask 应用上下文:没有上下文时的 RuntimeError;current_app 是 LocalProxy;g 每个请求都是新的;一次请求里信号与回调的实际顺序;请求上下文自带应用上下文;嵌套与命令行里的上下文。
验证 Flask 请求上下文:没有时的 RuntimeError;test_request_context 构造的 WSGI environ;push 时打开会话与做 URL 匹配(404 到分发时才抛出);无 SECRET_KEY 的 NullSession;线程里需要 copy_current_request_context;with client 保留上下文;背后是两个 ContextVar。
验证 current_app、request、g 的实现:它们是 LocalProxy,背后是 ContextVar,每个线程各自取到自己的对象;通过代理访问约是直接访问的 9.8 倍;g 的接口与自定义;代理不能传给别的线程。
验证上下文隔离:ContextVar 在新线程里不继承(thread_inherit_context=0);asyncio 任务各复制一份;threading.local 在协程间会串;werkzeug.Local 底层是 ContextVar;gunicorn 8 线程下 16 个并发请求 g 读回 0 个错值;线程池要用 copy_context 才带得上请求。
用 sys.setprofile 记录一次请求经过的 Flask 内部函数:wsgi_app → push → full_dispatch_request → finalize_request → pop;展示 3.1.3 的 wsgi_app 源码;每个空的 before_request 钩子约 0.55 µs。
实测 before_request / after_request / teardown_request:正常、abort、视图异常、before 直接返回、404、静态文件各种情况下谁执行;多个钩子的先后顺序;蓝图钩子的作用范围;after_request 返回 None 会 TypeError;TESTING 时异常传出则 after_request 不执行。
Flask 3.1.3 已经没有 before_first_request:改用应用工厂里初始化(一次),或 before_request 加锁(不加锁时 12 个并发请求执行了 12 次 init);gunicorn 3 个 worker 各执行一次,--preload 变成 1 次,线程锁挡不住多进程。
实测 Flask 异常处理器的匹配:MRO 最近的类优先;Exception 处理器也会接住 HTTP 异常,HTTPException 优先于它,状态码处理器优先于 HTTPException;403 与 Forbidden 是同一个键;蓝图处理器只管蓝图内的路由,未匹配的 404 走应用的;500 的 original_exception;TESTING 下异常是否传出。
验证 abort():它抛出 werkzeug 的 HTTPException(31 个状态码)生成默认 HTML 错误页;可自定义描述;429/405/401 自带 Retry-After/Allow/WWW-Authenticate;可传 Response 或 redirect;未知状态码 LookupError;可往 app.aborter.mapping 注册自定义状态码。
实测视图返回值的转换规则:str/bytes/dict/list/元组三种形态/生成器/Response 各得到什么状态与 Content-Type;None、int、set、dataclass、4 元组的 TypeError 原文;make_response 手动设置 cookie;生成器没有 Content-Length;response_class 统一加头。