如何防止FastAPI启动时初始化代码被重复执行两次?
重复执行的核心原因是uvicorn.run("main:app")的导入逻辑:运行python main.py时,主进程首先执行一遍main.py所有顶层代码,触发第一次打印;随后uvicorn根据传入的"main:app"字符串重新导入main模块获取app对象,导入过程会再次执行一遍模块顶层代码,触发第二次打印。
如果启动时开启了reload=True,uvicorn会额外启动监控子进程,也会触发代码重复加载,本质都是模块被多次导入导致的。
方案1:修改uvicorn启动参数,避免二次导入
把uvicorn.run的第一个参数从模块路径字符串改为直接传入app实例,跳过重新导入模块的步骤:
from fastapi import FastAPI import uvicorn app = FastAPI() print('*' * 80) @app.get("/") async def index(): return {"message": "Welcome to API!"} if __name__ == '__main__': # 直接传app对象,不要传"main:app"字符串 uvicorn.run(app, host="localhost", port=8000)
修改后直接运行python main.py时,顶层代码只会执行一次。
方案2:用FastAPI生命周期事件管理重开销初始化(推荐)
加载ML模型这类全局只需要执行一次的逻辑,不要写在模块顶层或者if __name__ == '__main__'块里,统一放到FastAPI的lifespan生命周期函数中,不管你是直接运行脚本、还是用命令行uvicorn main:app启动、或是多worker部署,这部分逻辑都只会在应用启动时执行一次,还能统一处理应用关闭时的资源释放:
from contextlib import asynccontextmanager from fastapi import FastAPI import uvicorn # 全局变量存放加载好的模型等重资源 large_ml_model = None @asynccontextmanager async def lifespan(app: FastAPI): # 应用启动时执行,仅运行一次 global large_ml_model print('*' * 80) # 替换为实际的模型加载逻辑 # large_ml_model = load_your_heavy_model() yield # 应用关闭时执行,做资源清理 large_ml_model = None app = FastAPI(lifespan=lifespan) @app.get("/") async def index(): return {"message": "Welcome to API!"} if __name__ == '__main__': uvicorn.run(app, host="localhost", port=8000)
方案3:非必要不开启reload模式
开发时如果开了reload=True,uvicorn会启动监控进程和应用进程两个进程,两个进程都会加载代码,会导致顶层逻辑执行两次。生产环境必须关闭reload,开发场景下如果开启reload,更要把重初始化逻辑放到lifespan中,避免重复加载浪费内存。
提示:不要把重初始化逻辑写在
if __name__ == '__main__'块中,当你用命令行uvicorn main:app启动服务时,这个块里的代码完全不会执行,会导致初始化逻辑失效。
内容的提问来源于stack exchange,提问作者abhinavkulkarni

