必须用 lifespan 参数配 @asynccontextmanager,因 @app.on_event 已废弃;模块顶层初始化会导致测试卡顿、热重载失败、多进程状态不一致;lifespan 精确控制初始化时机,yield 前启动、后清理,资源挂 app.state 安全共享。

必须用 lifespan 参数配 @asynccontextmanager,@app.on_event("startup") 和 @app.on_event("shutdown") 已被官方标记为废弃,生产环境别再用。
为什么不能直接写 global 变量初始化
把模型加载、数据库连接写在模块顶层(比如 model = load_model())看似简单,但会导致:测试时 import 就卡住、热重载失败、多进程下状态不一致。Lifespan 的核心价值不是“多写几行代码”,而是把初始化时机精确锚定在“ASGI server 真正开始接收请求前”这一瞬间——早一秒不行,晚一秒也不行。
- 模块导入时执行 → 所有测试/CI/IDE 静态分析都会触发,毫无必要
-
@app.on_event启动异常时,shutdown可能根本不执行,资源泄漏 - Lifespan 用
try/finally包裹 yield 后逻辑,关机信号(SIGTERM)或 Ctrl+C 都能触发清理
lifespan 函数里 yield 前后分别做什么
yield 是 Lifespan 的分水岭:前面是启动阶段(只执行一次,且必须完成才开始接请求),后面是关闭阶段(只执行一次,且必定执行)。它不是装饰器,不能加 async def 就完事,必须用 @asynccontextmanager 包装。
- yield 前:建连接池、
await model.load()、预热缓存、启动后台任务(如asyncio.create_task()) - yield 后:调用
pool.close()、await model.unload()、await task.cancel()、刷盘日志 - yield 可带参数,比如
yield {"db": db_pool, "model": model},但通常更推荐挂到app.state上供路由访问
怎么把初始化的资源传给路由函数用
别用全局变量传,也别靠闭包捕获,统一走 app.state。这是 FastAPI 官方明确支持的跨生命周期状态传递方式,且线程/协程安全。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 启动阶段写入:
app.state.db = db_pool、app.state.model = loaded_model - 路由中读取:
app.state.db.execute(...)或app.state.model.predict(...) - 别在
lifespan函数外定义db = None再赋值,这种模式在多 worker(如 uvicorn --workers 4)下会出错 - 如果要用类型提示,可在创建 app 后加注解:
app.state.db: AsyncConnectionPool
测试 Lifespan 逻辑是否生效
用 TestClient 必须配合 with 语句,否则 lifespan 根本不触发。很多同学写单元测试时漏了这层上下文,结果测的其实是“没初始化的状态”。
- 正确写法:
with TestClient(app) as client:→ 进入时启动 lifespan,退出时执行清理 - 错误写法:
client = TestClient(app)→ lifespan 完全不运行 - 验证点:进
with前检查app.state.db is None,进之后检查app.state.db is not None,出with后检查清理逻辑是否执行(比如连接池是否 closed)
最易忽略的一点:Lifespan 函数本身不能有未处理的 awaitable 悬停,比如忘记 await 一个异步初始化调用,会导致启动卡死且无报错——uvicorn 日志只显示 “started”,但实际没 ready。遇到启动慢,先检查所有 await 是否都写了。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










