生命周期事件¶
您可以定义在应用启动前执行的逻辑(代码)。这意味着此代码将只执行一次,在应用开始接收请求之前。
同样地,您可以定义在应用关闭时执行的逻辑(代码)。在这种情况下,此代码将只执行一次,在处理了可能许多请求之后。
因为这段代码在应用开始接收请求之前执行,并在处理完请求之后立即执行,所以它涵盖了整个应用的生命周期(“生命周期”这个词稍后会很重要 😉)。
这对于设置整个应用所需的资源非常有用,这些资源在请求之间是共享的,并且/或者您需要在之后进行清理。例如,数据库连接池,或者加载共享的机器学习模型。
用例¶
让我们从一个示例用例开始,然后看看如何用它来解决问题。
假设您有一些机器学习模型,您想用它们来处理请求。🤖
同样的模型在请求之间是共享的,因此不是每个请求一个模型,也不是每个用户一个模型,或者类似的情况。
假设加载模型可能需要相当长的时间,因为它必须从磁盘读取大量数据。因此,您不希望为每个请求都这样做。
您可以在模块/文件的顶层加载它,但这同样意味着即使您只是运行一个简单的自动化测试,它也会加载模型,那么这个测试就会很慢,因为它必须等待模型加载完成后才能运行代码中独立的部分。
这就是我们要解决的问题,我们将在处理请求之前加载模型,但仅在应用开始接收请求之前,而不是在代码加载时。
生命周期¶
您可以使用 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
参数,则 startup
和 shutdown
事件处理程序将不再被调用。它要么是完全 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()
不使用 async
和 await
。
因此,我们使用标准的 def
而不是 async def
来声明事件处理函数。
startup
和 shutdown
同时使用¶
您的 启动 和 关闭 逻辑很有可能是相互关联的,您可能想要启动某项内容然后完成它,获取资源然后释放它等等。
在不共享逻辑或变量的独立函数中执行此操作会更困难,因为您需要将值存储在全局变量或使用类似技巧。
因此,现在建议改用上面解释的 lifespan
。
技术细节¶
给好奇的技术宅一个技术细节。🤓
底层,在 ASGI 技术规范中,这是 生命周期协议 的一部分,它定义了名为 startup
和 shutdown
的事件。
子应用¶
🚨 请记住,这些生命周期事件(启动和关闭)将仅为主应用执行,而不是为子应用 - 挂载执行。