跳到内容

生命周期事件

您可以定义在应用启动前执行的逻辑(代码)。这意味着此代码将只执行一次,在应用开始接收请求之前

同样地,您可以定义在应用关闭时执行的逻辑(代码)。在这种情况下,此代码将只执行一次,在处理了可能许多请求之后

因为这段代码在应用开始接收请求之前执行,并在处理完请求之后立即执行,所以它涵盖了整个应用的生命周期(“生命周期”这个词稍后会很重要 😉)。

这对于设置整个应用所需的资源非常有用,这些资源在请求之间是共享的,并且/或者您需要在之后进行清理。例如,数据库连接池,或者加载共享的机器学习模型。

用例

让我们从一个示例用例开始,然后看看如何用它来解决问题。

假设您有一些机器学习模型,您想用它们来处理请求。🤖

同样的模型在请求之间是共享的,因此不是每个请求一个模型,也不是每个用户一个模型,或者类似的情况。

假设加载模型可能需要相当长的时间,因为它必须从磁盘读取大量数据。因此,您不希望为每个请求都这样做。

您可以在模块/文件的顶层加载它,但这同样意味着即使您只是运行一个简单的自动化测试,它也会加载模型,那么这个测试就会很慢,因为它必须等待模型加载完成后才能运行代码中独立的部分。

这就是我们要解决的问题,我们将在处理请求之前加载模型,但仅在应用开始接收请求之前,而不是在代码加载时。

生命周期

您可以使用 FastAPI 应用的 lifespan 参数和“上下文管理器”(我稍后会展示那是什么)来定义这种 启动关闭 逻辑。

让我们从一个例子开始,然后详细了解它。

我们这样创建一个带有 yield 的异步函数 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"](x)
    return {"result": result}

在这里,我们通过将(伪)模型函数放在包含机器学习模型的字典中,在 yield 之前,来模拟加载模型的昂贵启动操作。这段代码将在应用开始接收请求之前,在启动期间执行。

然后,紧接着 yield 之后,我们卸载模型。这段代码将在应用处理完请求之后,在关闭之前执行。例如,这可以释放内存或 GPU 等资源。

提示

shutdown 会在您停止应用时发生。

也许您需要启动一个新版本,或者您只是厌倦了运行它。🤷

生命周期函数

首先要注意的是,我们正在定义一个带有 yield 的异步函数。这与带有 yield 的依赖项非常相似。

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"](x)
    return {"result": result}

函数的第一部分,即 yield 之前的部分,将在应用启动之前执行。

yield 之后的部分将在应用完成之后执行。

异步上下文管理器

如果您检查,该函数被 @asynccontextmanager 装饰。

这将该函数转换为一个名为“异步上下文管理器”的东西。

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"](x)
    return {"result": result}

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 之后的代码。

在我们上面的代码示例中,我们没有直接使用它,而是将其传递给 FastAPI 以供其使用。

FastAPI 应用的 lifespan 参数接受一个异步上下文管理器,因此我们可以将新的 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"](x)
    return {"result": result}

替代事件(已弃用)

警告

处理 启动关闭 的推荐方式是使用 FastAPI 应用的 lifespan 参数,如上所述。如果您提供了 lifespan 参数,则 startupshutdown 事件处理程序将不再被调用。它要么是完全 lifespan,要么是所有事件,不能两者兼顾。

您可以跳过这部分。

还有另一种定义在 启动关闭 期间执行的逻辑的方式。

您可以定义需要在应用启动前或应用关闭时执行的事件处理程序(函数)。

这些函数可以用 async def 或普通的 def 声明。

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 事件处理函数将使用一些值初始化“数据库”(只是一个 dict)。

您可以添加多个事件处理函数。

并且您的应用将不会开始接收请求,直到所有的 startup 事件处理程序都已完成。

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")


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

在这里,shutdown 事件处理函数将把文本行 "Application shutdown" 写入文件 log.txt

信息

open() 函数中,mode="a" 表示“追加”,因此,该行将被添加到文件中已有的内容之后,而不会覆盖之前的内容。

提示

请注意,在这种情况下,我们使用的是与文件交互的标准 Python open() 函数。

因此,它涉及 I/O(输入/输出),这需要“等待”内容写入磁盘。

open() 不使用 asyncawait

因此,我们使用标准的 def 而不是 async def 来声明事件处理函数。

startupshutdown 同时使用

您的 启动关闭 逻辑很有可能是相互关联的,您可能想要启动某项内容然后完成它,获取资源然后释放它等等。

在不共享逻辑或变量的独立函数中执行此操作会更困难,因为您需要将值存储在全局变量或使用类似技巧。

因此,现在建议改用上面解释的 lifespan

技术细节

给好奇的技术宅一个技术细节。🤓

底层,在 ASGI 技术规范中,这是 生命周期协议 的一部分,它定义了名为 startupshutdown 的事件。

信息

您可以在 Starlette 的生命周期文档 中阅读更多关于 Starlette lifespan 处理程序的信息。

包括如何处理可在代码其他区域使用的生命周期状态。

子应用

🚨 请记住,这些生命周期事件(启动和关闭)将仅为主应用执行,而不是为子应用 - 挂载执行。