首页
/ FastAPI 进阶依赖指南:可参数化依赖(Callable 实例)与 `yield` 依赖生命周期演进详解

FastAPI 进阶依赖指南:可参数化依赖(Callable 实例)与 `yield` 依赖生命周期演进详解

2026-09-06 19:08:08作者:宗隆裙

本篇技术指南围绕 FastAPI 官方文档中的“进阶依赖”(Advanced Dependencies)主题展开,聚焦两大核心能力:一是通过 callable(可调用)实例实现带参数的依赖,解决“同一套校验逻辑、不同固定参数”需要重复声明大量函数/类的问题;二是深入剖析 yield 的依赖scopeStreamingResponseexcept、后台任务等场景下的生命周期演进与迁移建议。读完你将掌握可参数化依赖的完整写法,理解 Depends(scope="function") 与默认 scope="request" 的差异,并能在升级旧版 FastAPI 应用时准确处理 yield 依赖的资源释放语义。

文档源代码示例位于仓库 docs_src/dependencies 目录,实现源码可对照 fastapi/params.pyfastapi/dependencies 深入阅读。

一、从“固定依赖”到“可参数化依赖”

在 FastAPI 的依赖注入体系中,此前教程中看到的依赖都是一个 固定的函数或类——每次使用 Depends() 时,依赖本身的行为是确定的、写死的。

但在真实业务里,我们常常会遇到这样的需求:希望同一套校验/处理逻辑能接收不同的配置参数,从而避免为每一种参数组合声明大量几乎重复的函数或类。

原文档给出的是一个非常典型的场景:

我们希望有一个依赖,用于检查查询参数 q 中是否包含某段“固定内容”。同时,我们又希望这段“固定内容”是可以参数化的。

如果按“固定函数/固定类”的思路,每换一个固定内容(例如 "bar""foo")就要新写一个函数或类,代码会迅速膨胀。而 Python 本身提供了一种优雅的机制来解决这个问题——类的可调用实例(callable instance)

二、可调用实例(Callable Instance)实现参数化依赖

2.1 什么是“可调用的实例”

Python 中,类本身天然是可调用的(调用它即实例化)。但这里要强调的是:让某个类的“实例”也变得可调用,而非类本身。

实现方式是在类中声明一个 __call__ 方法。有了它,实例就能像函数一样被调用,例如 checker(...)。原文档对应的示例代码位于 docs_src/dependencies/tutorial011_an_py310.py,其核心逻辑如下:

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


class FixedContentQueryChecker:
    def __init__(self, fixed_content: str):
        self.fixed_content = fixed_content

    def __call__(self, q: str = ""):
        if q:
            return self.fixed_content in q
        return False


checker = FixedContentQueryChecker("bar")


@app.get("/query-checker/")
async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]):
    return {"fixed_content_in_query": fixed_content_included}

从 FastAPI 的角度看,这个 __call__ 方法有两重作用:

  1. 用来检查额外的参数和子依赖——FastAPI 会像解析普通依赖函数一样解析 __call__ 的签名(这里的 q: str = "");
  2. 最终被调用来产出一个值——该返回值会作为依赖解析结果,注入到 path operation function 的对应参数中(此处的 fixed_content_included)。

2.2 通过 __init__ 参数化实例

关键技巧在于:__init__ 声明“实例级参数”来参数化依赖

def __init__(self, fixed_content: str):
    self.fixed_content = fixed_content

此时,__init__纯 Python 层面的普通构造逻辑——FastAPI 永远不会去触碰或关心 __init__ 中的内容,它只由我们自己的代码在创建实例时直接调用。这正是“参数化”得以成立的分工:

  • __init__ 负责把配置(如 fixed_content)注入实例,供你按场景定制;
  • __call__ 负责接收请求相关的参数(如 q),交由 FastAPI 当作依赖函数解析执行。

2.3 创建实例并作为依赖使用

先手工创建带固定参数的实例:

checker = FixedContentQueryChecker("bar")

此时该依赖实例内部已携带 "bar",保存在属性 checker.fixed_content 中。接下来,使用实例本身而不是类:

async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]):
    return {"fixed_content_in_query": fixed_content_included}

注意这里写的是 Depends(checker) 而不是 Depends(FixedContentQueryChecker)——依赖对象是实例 checker,而不是类

FastAPI 在解析该依赖时,等价于执行:

checker(q="somequery")

q 来自真实请求的查询参数,返回值(布尔值,表示固定内容是否被包含)会被注入到 path operation function 的参数 fixed_content_included 中。若请求形如 GET /query-checker/?q=somequerybar,返回值即 {"fixed_content_in_query": true}

2.4 真实世界同类实现:安全模块正是这样写的

原文档特别提示:这套写法看起来“刻意且复杂”,示例是为了展示原理而故意简化;在安全(Security)章节中,有一批工具函数/类正是以完全相同的方式实现。理解了 callable 实例依赖,就等于理解了这些安全工具底层的运作方式。

fastapi/security 源码中可找到大量证据。例如 fastapi/security/oauth2.py 中的 OAuth2PasswordBearer.__call__

async def __call__(self, request: Request) -> str | None:
    authorization = request.headers.get("Authorization")
    if not authorization:
        if self.auto_error:
            raise self.make_not_authenticated_error()
        else:
            return None
    return authorization

在教程中,用法正是“先参数化创建实例,再作为依赖注入”:

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    ...

OAuth2PasswordBearer(tokenUrl="token") 就是在用 __init__ 参数化(配置 tokenUrl、auto_error 等),随后 FastAPI 调用其 __call__ 完成请求期校验。同样的 __call__ 模式还出现在 fastapi/security/http.py(如 HTTPBearerHTTPBasic)与 fastapi/security/api_key.py(API Key Header/Query/Cookie)中,相关中文文档可查阅 docs/es/docs/tutorial/security 下的教程。若你还想查看其他可调用安全依赖如何被用作实例,可阅读 docs/es/docs/advanced/security/oauth2-scopes.md

三、yield 依赖:scopeHTTPExceptionexcept 与后台任务的演进

3.1 这部分内容什么时候才用得上?

原文档开篇即给出明确警告:大多数应用并不需要这些技术细节。它们主要服务于两类人:

  • 0.121.0 之前的旧版 FastAPI 应用升级、正遭遇 yield 依赖相关问题的开发者;
  • 需要深刻理解依赖生命周期、以便排查资源释放时机的进阶用户。

yield 的依赖在 FastAPI 各版本中经历了一系列演进,用于覆盖不同用例并修复缺陷。下面按文档顺序梳理这些“历史变更”——它们直接决定了今天 yield 依赖的退出代码(yield 之后的清理逻辑)在何时执行。

3.2 yield 依赖与 scope(0.121.0 起支持)

FastAPI 0.121.0 起,为带 yield 的依赖新增了 Depends(scope="function") 支持。Dependsscope 字段在源码中定义为字面量类型(见 fastapi/params.py):

class Depends:
    dependency: Callable[..., Any] | None = None
    use_cache: bool = True
    scope: Literal["function", "request"] | None = None

两种取值决定了依赖的“退出代码”(yield 之后的部分)何时执行:

scope 取值 依赖启动时机 退出代码执行时机
"function"(需要显式指定) 处理该请求的 path operation function 执行前 path operation function 结束后、响应发回客户端之前立刻执行
"request"默认值 同上,请求处理前 响应已经发送给客户端之后再执行

示例(详见 docs_src/dependencies/tutorial008e_an_py310.py):

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


def get_username():
    try:
        yield "Rick"
    finally:
        print("Cleanup up before response is sent")


@app.get("/users/me")
def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]):
    return username

关于 scope 还有一条子依赖约束需要留意:

  • 声明为 scope="request"(默认)的依赖,其所有子依赖也必须都是 "request" 作用域
  • scope="function" 的依赖,其子依赖既可以是 "function" 也可以是 "request"

原因在于:任何依赖都需要能先于其子依赖执行退出代码,因为退出时它可能仍要用到子依赖提供的资源。

关于“提前退出(Early exit)与 scope”更完整的时序图与说明,可继续阅读 docs/es/docs/tutorial/dependencies/dependencies-with-yield.md 教程中的 “Salida temprana y scope” 一节。

3.3 yield 依赖与 StreamingResponse 的技术细节(0.118.0 回退)

0.118.0 之前,带 yield 的依赖会在 path operation function 返回后、发送响应之前就执行退出代码。这样设计的初衷是:避免在等待响应“走完网络”期间不必要地持有资源。

但这个设计带来了副作用:如果你返回的是 StreamingResponse,则依赖的退出代码可能已经提前执行完毕。举例来说,如果数据库会话放在 yield 依赖中,那么流式传输数据时该 StreamingResponse无法再使用这个会话——因为会话已在 yield 后的退出代码里被关闭。

0.118.0 对该行为进行了回退:让 yield 之后的退出代码改为在响应发送完成之后执行。

原文档附注指出:这一回退后的行为与 0.106.0 之前的旧行为非常相似,但针对若干边界情况做了改进与 bug 修复。

3.4 需要“提前退出”的特定用例:手动关闭会话

某些特定条件下,旧行为(发送响应前执行退出代码)反而更省资源。原文档给出一类典型场景:

代码在 yield 依赖中使用数据库会话仅用于校验用户path operation function 中不再使用该会话;同时响应传输很慢(如一个缓慢输出数据、且不使用数据库的 StreamingResponse)。

此时若按默认 scope="request",数据库会话会被一直持有到响应完全发送完毕;但既然响应阶段根本用不到数据库,持有会话就属于浪费。

先看问题形态的示例(docs_src/dependencies/tutorial013_an_py310.py):

import time
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from sqlmodel import Field, Session, SQLModel, create_engine

engine = create_engine("postgresql+psycopg://postgres:postgres@localhost/db")


class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str


app = FastAPI()


def get_session():
    with Session(engine) as session:
        yield session


def get_user(user_id: int, session: Annotated[Session, Depends(get_session)]):
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=403, detail="Not authorized")


def generate_stream(query: str):
    for ch in query:
        yield ch
        time.sleep(0.1)


@app.get("/generate", dependencies=[Depends(get_user)])
def generate(query: str):
    return StreamingResponse(content=generate_stream(query))

这里的资源释放链路是:get_user 中抛出的 HTTPException(status_code=403, detail="Not authorized") 用于校验;get_sessionyield 之后自动关闭 Session 的退出代码(示例第 19~21 行的 with 块结束逻辑),会等到慢速响应全部发送完之后才执行;而 generate_stream()(第 30~38 行,逐字符输出并 time.sleep(0.1) 模拟慢速流)并不使用数据库会话。

如果使用 SQLModel(或 SQLAlchemy)且恰好命中这类场景,可在不再需要会话时手动显式关闭(对比 docs_src/dependencies/tutorial014_an_py310.py,其差别在于校验完成后立即调用 session.close()):

def get_user(user_id: int, session: Annotated[Session, Depends(get_session)]):
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=403, detail="Not authorized")
    session.close()

这样,会话会提前释放数据库连接,供其他请求复用。

需要强调的是:如果你的用例确实需要更通用的“从 yield 依赖中提前退出”机制,原文档建议在 FastAPI 的 Discussion 中提出具体用例。若存在足够有说服力的早期关闭(early closing)需求,上游会考虑新增一种“选择加入早期关闭”的新方式——也就是说,目前并没有一个通用开关来开启这一旧行为,手动关闭资源是文档给出的落地路径。

3.5 yield 依赖与 except 的技术细节(0.110.0 变更)

0.110.0 之前,如果在带 yield 的依赖中通过 except 捕获了异常,却没有再次 raise,该异常仍会被自动抛出/转发到任何异常处理器(exception handlers)或内部服务器错误处理器。

0.110.0 修改了这一行为,目的有两个:

  1. 修复“被转发的异常若没有对应处理器(即内部服务器错误)会导致未受控的内存消耗”的问题;
  2. 使其与 普通 Python 代码的惯例保持一致——捕获后不重新抛出,就应当视为已处理。

对升级用户而言,这意味着如果你在 except 块中捕获了异常且希望交给上层异常处理器统一处理,必须显式地重新抛出(raise

3.6 后台任务与 yield 依赖的技术细节(0.106.0 变更)

0.106.0 之前yield 之后无法抛出异常,依赖退出代码在响应发送之后才执行,因此 异常处理器(自定义异常处理器)此时早已执行完毕。这样设计的初衷,主要是允许在后台任务中复用依赖 yield 出来的对象——因为退出代码会等后台任务结束后才执行。

0.106.0 修改了该行为,意图同样是“不在等待响应传输期间持有资源”。

原文档附带的建议非常实用:

后台任务通常是一组相互独立的逻辑,应当单独处理、自带资源(例如它自己的数据库连接)。这样做通常会得到更干净的代码。

因此,如果旧代码依赖“yield 对象可在后台任务中使用”这一行为,现在应当:

  1. 在后台任务函数内部自建资源(例如新建一个数据库会话),而不是复用 yield 依赖里的会话;
  2. 后台任务内部只使用不依赖 yield 依赖资源的数据
  3. 不要把数据库对象直接作为参数传给后台任务函数,而是传对象的 ID,在后台任务内部用新会话重新查出该对象再处理。

3.7 演进时间线与迁移自查

综合原文档,带 yield 依赖的关键行为变更可按版本归纳为一张速查表,便于升级排查:

版本节点 变更内容 影响
0.106.0 yield 之后可抛异常;退出代码执行时机调整为发送响应前/不再等后台任务 后台任务需自带资源,传入对象 ID 而非对象
0.110.0 except 捕获后不重新抛出时,不再自动转发异常 需显式 raise 才能交给异常处理器
0.118.0 回退:退出代码改为响应发送完成后执行,修复 StreamingResponse 无法使用已关闭会话的问题 慢速流/长连接场景注意资源持有时间
0.121.0 新增 Depends(scope="function") 支持 可用 "function" 作用域获得“路径函数结束即清理”的精确控制

若要验证这些行为在当前版本代码库中的具体表现,可以参考对应测试用例,例如 tests/test_dependency_yield_scope.py 中针对 scope="function" / scope="request" 的退出代码顺序断言,以及 tests/test_dependency_yield_scope_websockets.py 中 WebSocket 场景下的等价覆盖。

四、小结

本文围绕 FastAPI 进阶依赖的两条主线完成了闭环:

  • 可参数化依赖:通过 __init__ 携带配置 + __call__ 交由 FastAPI 解析执行,从而以“类的一个实例”作为 Depends 的依赖对象。这一模式不仅是定制校验逻辑的利器,更是 fastapi/securityOAuth2PasswordBearerHTTPBearer、API Key 等安全依赖的底层实现方式。
  • yield 依赖生命周期:从 0.106.0、0.110.0、0.118.0 到 0.121.0 的多次演进,核心矛盾始终是“尽早释放资源”与“确保响应/后台任务仍能使用被释放资源”之间的平衡;当前推荐的落地方案是:默认使用 scope="request",需要提前清理时用 Depends(scope="function") 或在依赖内部显式 close() 资源,并在后台任务中自建资源、只传 ID。

理解这两条主线,就能在编写高阶依赖注入逻辑时拥有清晰的心智模型,也能在升级旧版 FastAPI 应用时快速定位由依赖退出时机变化引发的问题。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388