首页 / FastAPI 入门教程 / 生命周期事件

FastAPI 入门教程

生命周期事件

本教程共 50 篇 · 第 43 篇 · 更新于 2026-08-12 · 约 6 分钟阅读

FastAPIFastAPI 入门教程生命周期lifespanstartupshutdown

本节目标:学会在 FastAPI 应用启动和关闭时执行初始化与清理代码,并理解新版 lifespan 写法为何取代了旧的 on_event 装饰器。

写接口时,有些代码不需要每次请求都跑。比如连数据库的连接池、加载一个很大的机器学习模型、读取一份共享的配置。这些东西只需要在应用启动时准备好一次,然后在应用关闭时释放掉。FastAPI 把这类时机称为「生命周期」。

43-1 什么时候需要生命周期逻辑

设想你有一个接口要调用一个机器学习模型。这个模型会被所有请求共用,不是每个请求单独加载一份。加载模型很慢,要从磁盘读很多数据。

如果你把加载写在了文件最顶层,那么哪怕只是跑一个自动化测试,也会先把模型加载一遍,测试变得奇慢。更好的做法是:只在应用真正开始接收请求之前,才去加载模型。应用关闭时再把模型卸载,释放显存和内存。

这类需求都适合放在生命周期里:

  • 启动时建立数据库连接池,关闭时关闭它
  • 启动时预加载共享数据到内存,关闭时清空
  • 启动时连接消息队列,关闭时断开

43-2 推荐写法:lifespan 上下文管理器

新版 FastAPI 推荐用 lifespan 参数来管理启动和关闭逻辑。它本质是一个「异步上下文管理器」,用 yield 把代码分成两段:yield 之前的代码在启动前执行,yield 之后的代码在关闭前执行。

from contextlib import asynccontextmanager
from fastapi import FastAPI


# 模拟一个很重的模型加载过程
def load_model():
    return {"model_a": "已加载的假模型"}


# 模拟卸载模型
def unload_model(models):
    models.clear()


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动阶段:在接收请求之前执行
    models = load_model()
    print("应用已启动,模型加载完成")
    yield
    # 关闭阶段:在停止接收请求之后执行
    unload_model(models)
    print("应用已关闭,模型已释放")


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def root():
    return {"msg": "hello"}

重点看 lifespan 函数。它用 @asynccontextmanager 装饰,内部是一个 async 函数,中间有一个 yield。yield 之前的代码在应用「开始接收请求之前」运行一次;yield 之后的代码在应用「停止接收请求之后」运行一次。

yield 后面不接任何值也没关系,它的作用只是把上下文一分为二。最后我们把 lifespan= lifespan 传给 FastAPI(...),FastAPI 就会在合适的时机调用它。

Tip

上下文管理器的概念你其实用过:Python 里的 with open("file.txt") as f: 就是上下文管理器。它在进入 with 块前打开文件,退出后自动关闭。asynccontextmanager 是异步版的同一个思想。

43-3 启动代码与关闭代码怎么分工

回到模型那个例子。我们把「加载」放在 yield 之前,「卸载」放在 yield 之后,二者共享同一个 models 变量,因为它们在同一个函数作用域里,启动和关闭两段之间不需要用全局变量传来传去。不过请求处理函数如果也要访问这些资源,仍需要把它存到模块级容器(或通过依赖注入暴露),lifespan 内部的共享作用域只覆盖启动和关闭两段。

from contextlib import asynccontextmanager
from fastapi import FastAPI

ml_models = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动:把模型放进共享字典
    ml_models["model_a"] = "fake_model_object"
    yield
    # 关闭:清空字典,释放资源
    ml_models.clear()


app = FastAPI(lifespan=lifespan)


@app.get("/predict")
async def predict():
    # 请求处理函数里可以直接用 ml_models
    return {"model": ml_models.get("model_a")}

这样模型在应用启动后一直留在内存里,每个请求都能直接用,应用关掉时才清空。

43-4 旧写法 on_event 已弃用

曾经 FastAPI 提供 @app.on_event("startup")@app.on_event("shutdown") 两个装饰器来写启动和关闭逻辑。下面是旧写法示例,你可能在老教程里见过:

from fastapi import FastAPI

items = {}


@app.on_event("startup")
async def on_startup():
    # 启动时填充一个内存字典
    items["key"] = "value"


@app.on_event("shutdown")
def on_shutdown():
    # 关闭时把数据写入日志文件
    with open("log.txt", mode="a") as f:
        f.write("Application shutdown\n")
Warning

这种 @app.on_event 写法已经被官方标记为弃用。如果你给应用传了 lifespan 参数,那么 startupshutdown 事件处理函数将不再被调用。二者只能选一种,不能混用。新项目请一律使用上面讲的 lifespan

旧写法有两个明显缺点。第一,启动和关闭的代码被拆成两个函数,若它们要共享状态,就得靠全局变量,容易出错。第二,FastAPI 官方希望统一到 ASGI 标准的 lifespan 协议上,方便和其他工具协作。所以除非你在维护老代码,否则不要再写 on_event

Note

关于阻塞 I/O:旧文档示例里关闭时写文件用了普通 def 而不是 async def,因为 open() 不是异步函数。如果你在 lifespan 里做文件读写、同步网络请求这类阻塞操作,应当用普通 def 函数或在线程池里执行,避免卡住事件循环。

43-5 技术细节与子应用

在底层,这套机制遵循 ASGI 规范的「Lifespan 协议」,定义了 startupshutdown 两个事件。FastAPI 的 lifespan 最终也是映射到这两个事件上。

还要记住一点:生命周期事件只在主应用上触发,不会在通过 app.mount() 挂载的子应用(Sub Applications / Mounts)上触发。如果你用了挂载,要把需要的初始化逻辑放进主应用的 lifespan 里。

另外要留意 lifespan 与多进程部署的关系。用 fastapi run --workers 4 起四个工作进程时,lifespan 会在每个进程里各跑一遍,而不是全局只跑一次。这意味着预加载的模型、建立的连接池都是每进程一份,内存占用要按进程数乘。更要紧的是,像「启动时建表」「启动时写一条初始化记录」这类只应执行一次的操作,放在 lifespan 里会被重复执行。这类任务更适合抽成独立脚本,在部署流程中单独运行一次。

43-6 小结

启动和关闭逻辑用 lifespan 上下文管理器最干净。yield 之前放启动代码,之后放关闭代码,二者共享变量。旧的 @app.on_event("startup"/"shutdown") 已弃用,不要在新代码里使用。把重资源的准备和释放放进 lifespan,既提升性能又方便测试。