首页
/ FastAPI Lifespan 事件完全指南:用 lifespan 参数管理应用启动与关闭逻辑

FastAPI Lifespan 事件完全指南:用 lifespan 参数管理应用启动与关闭逻辑

2026-09-06 14:22:44作者:毕习沙Eudora

在 FastAPI 中,很多资源(数据库连接池、机器学习模型、缓存实例等)需要在应用开始接收请求之前一次性初始化,并在应用停止处理请求之后统一清理。FastAPI 通过 lifespan 参数和异步上下文管理器(async context manager)机制,让你把这类"应用生命周期(lifespan)"逻辑集中到一处管理。读完本文,你将掌握:如何编写 startup/shutdown 共享逻辑、@asynccontextmanager 的工作原理、已废弃的 @app.on_event 替代方案,以及子应用(Mount)下的注意事项。

典型使用场景:加载共享的机器学习模型

先看一个官方文档给出的经典用例。假设你有一个机器学习模型,需要用它来处理请求,且这个模型被所有请求共享——不是每个请求、每个用户各加载一份。加载模型可能非常耗时,因为它需要从磁盘读取大量数据,因此绝不能为每个请求都加载一次。

你可能会把模型加载代码写在模块的顶层,但这会导致一个问题:即使你只是运行一个简单的自动化测试,也会先花大量时间加载模型,拖慢本不需要它的测试代码。

我们要解决的就是这个矛盾:在应用开始接收请求之前、但在代码被加载(import)之后加载模型。这正是 startup 逻辑的职责。

推荐方案:lifespan 参数 + 异步上下文管理器

启动(startup)与关闭(shutdown)逻辑通过 FastAPI 应用的 lifespan 参数定义。完整示例可参考 官方示例源码,核心代码如下:

from contextlib import asynccontextmanager
from fastapi import FastAPI

def fake_answer_to_everything_ml_model(x: float):
    return x * 42

ml_models = {}

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Load the ML model
    ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
    yield
    # Clean up the ML models and release the resources
    ml_models.clear()

app = FastAPI(lifespan=lifespan)

@app.get("/predict")
async def predict(x: float):
    result = ml_models"answer_to_everything"
    return {"result": result}

这段代码用字典 ml_models 模拟"昂贵的启动操作"(加载模型):

  • yield 之前的代码在应用开始接收请求之前执行(即 startup 阶段)。示例中,把(假的)模型函数放进 ml_models 字典,模拟加载完成。
  • yield 之后的代码在应用结束处理所有请求之后shutdown 前一刻执行。示例中调用 ml_models.clear() 释放资源,例如释放内存或 GPU。

::: tip shutdown 会在你停止应用时发生——比如你要部署新版本,或者单纯不想再让它运行了。 :::

lifespan 函数:yield 划分的两段逻辑

首先注意,lifespan 是一个用 yield 定义的 async 函数,这与 FastAPI 中"带 yield 的依赖项(Dependencies)"的写法非常相似:

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 应用启动前执行
    ...
    yield
    # 应用结束后执行
    ...

函数体中 yield 之前的部分在应用启动前执行;yield 之后的部分在应用收尾后执行。这种"以 yield 为界"的结构,天然适合表达"先获取资源、后释放资源"的成对逻辑。

异步上下文管理器(async context manager)

上例中函数还带有一个 @asynccontextmanager 装饰器:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    ...

它把这个函数转换成一个异步上下文管理器(async context manager)

Python 中的上下文管理器是可以用于 with 语句的对象,例如 open()

with open("file.txt") as file:
    file.read()

较新版本的 Python 还支持异步上下文管理器,配合 async with 使用:

async with lifespan(app):
    await do_stuff()

当我们像上面这样创建一个(异步)上下文管理器时,它的行为是:进入 with 块之前,执行 yield 之前的代码;退出 with 块之后,执行 yield 之后的代码。

在示例代码中我们并没有直接 async with 它,而是把它交给 FastAPI 来使用。FastAPI 应用的 lifespan 参数接受的正是这样一个异步上下文管理器:

app = FastAPI(lifespan=lifespan)

从源码可以看到,FastAPI 应用构造函数lifespan 的类型标注为 Lifespan[AppType] | None,文档注释明确写着它"用单个上下文管理器取代 startupshutdown 函数"(A Lifespan context manager handler. This replaces startup and shutdown functions with a single context manager),并在 应用初始化 时原样透传给底层的 APIRouter

在应用状态中共享 lifespan 数据(补充)

如果需要在请求处理逻辑中访问 lifespan 里加载的资源,除了示例中使用的模块级字典 ml_models 之外,从源码结构看,FastAPI 还继承了 Starlette 的 app.state 对象(见 applications.py 中的 state 属性定义):它是整个应用生命周期内同一个对象,不随请求变化,可以把它当作跨请求共享的"全局状态"来存放 lifespan 阶段加载的数据。

替代方案:on_event 事件(已废弃)

⚠️ 重要提示:处理 startupshutdown推荐方式是上文描述的 lifespan 参数。如果你提供了 lifespan 参数,startupshutdown 事件处理函数将不再被调用——要么全用 lifespan,要么全用事件,二者不能混用。如果你的项目是新项目,这部分可以直接跳过。

替代方式是注册独立的事件处理函数。这些函数既可以用 async def 声明,也可以用普通 def 声明。

startup 事件

用事件 "startup" 声明一个在应用启动前运行的函数(示例见 startup 事件源码):

from fastapi import FastAPI

app = FastAPI()

items = {}


@app.on_event("startup")
async def startup_event():
    items["foo"] = {"name": "Fighters"}
    items["bar"] = {"name": "Tenders"}


@app.get("/items/{item_id}")
async def read_items(item_id: str):
    return items[item_id]

这里 startup 事件处理函数把若干初始数据写入 items 这个 dict(充当"数据库")。你可以注册多个 startup 处理函数;在所有 startup 事件处理函数执行完毕之前,应用不会开始接收请求。

shutdown 事件

用事件 "shutdown" 声明一个在应用关闭时运行的函数(示例见 shutdown 事件源码):

from fastapi import FastAPI

app = FastAPI()


@app.on_event("shutdown")
def shutdown_event():
    with open("log.txt", mode="a") as log:
        log.write("Application shutdown")

这里 shutdown 事件处理函数会向文件 log.txt 追加写入一行文本 "Application shutdown"

  • open() 中的 mode="a" 表示追加(append),新内容会写到文件末尾,不会覆盖原有内容。
  • 注意这里用的是标准 Python open(),涉及需要"等待"磁盘写入的 I/O 操作,但它本身不是 async 的。因此该事件处理函数声明为普通 def 而非 async def——这样 FastAPI 会在专门的线程中运行它,避免阻塞事件循环。

startupshutdown 为何难以配合

startupshutdown 的逻辑通常是有联系的:启动某样东西再关闭它、获取资源再释放它等。把它们拆在两个互相不共享变量、不共享逻辑的函数里会更困难——你往往需要借助全局变量或类似技巧来传递状态。这正是官方现在推荐改用 lifespan 的原因:yield 前后的代码处在同一个函数作用域内,共享同一个 app 引用,天然适合成对管理资源。

从源码可以确认这套旧机制确实已被标记废弃:FastAPI.on_eventAPIRouter.on_event 均带有 @deprecated 装饰器,提示"on_event is deprecated, use lifespan event handlers instead";同时 FastAPI 构造函数的 on_startup / on_shutdown 列表参数的文档注释也建议改用 lifespan 处理函数。对应的测试覆盖位于 tests/test_tutorial/test_events/(包含 test_tutorial001.pytest_tutorial002.pytest_tutorial003.py),验证了上述三种事件写法的端到端行为。

技术细节:ASGI Lifespan 协议

给好奇的技术细节:在 ASGI 技术规范层面,FastAPI 的这套机制对应其中的 Lifespan Protocol,该协议定义了 startupshutdown 两类事件。FastAPI 的 lifespan 参数最终由底层 Starlette 应用驱动执行;如果你想了解 Starlette 的 lifespan 处理器细节,以及如何在代码其他位置使用 lifespan 期间保存的状态,可进一步阅读 Starlette 官方文档中关于 Lifespan 的章节。

注意事项:子应用(Mounts)不会执行这些事件

务必记住:lifespan 事件(startup 和 shutdown)只会在主应用上执行一次,而不会为通过 mount() 挂载的**子应用(Sub Applications - Mounts)**执行。如果你把带有 lifespan 逻辑的应用挂载到另一个应用下,子应用的启动/关闭逻辑将不会触发——这类子应用的机制见 Sub Applications - Mounts。此外,测试场景下如何验证 startup/shutdown 逻辑,可参考 Testing Events 文档

小结

方案 写法 状态 适用场景
lifespan 参数 @asynccontextmanager + yieldFastAPI(lifespan=...) ✅ 推荐 startup/shutdown 逻辑需要共享变量(如获取并释放资源)
@app.on_event("startup" / "shutdown") 独立事件处理函数 ⚠️ 已废弃 仅需简单、彼此独立的启动或关闭逻辑;提供了 lifespan 后它们不会再被调用
FastAPI(on_startup=[...], on_shutdown=[...]) 构造函数传函数列表 ⚠️ 源码注释中建议改用 lifespan 遗留代码兼容

核心要点回顾:

  1. @asynccontextmanager 装饰的 async 函数中,yield 之前是 startup 逻辑,之后是 shutdown 逻辑;
  2. 把它通过 FastAPI(lifespan=lifespan) 传入即可,应用只会在开始接收请求前执行一次 startup、在结束时执行一次 shutdown;
  3. 避免在模块顶层加载昂贵资源,改用 lifespan 延迟到应用真正启动时加载;
  4. 挂载的子应用不触发 lifespan 事件,需特别注意资源管理的归属。
登录后查看全文
热门项目推荐
相关项目推荐