首页
/ FastAPI 教程:将 Python 类用作依赖(Class as Dependencies)——从 dict 到类型安全的依赖注入实践

FastAPI 教程:将 Python 类用作依赖(Class as Dependencies)——从 dict 到类型安全的依赖注入实践

2026-09-06 18:18:50作者:薛曦旖Francesca

依赖不一定非是函数不可:只要是一个 Python 可调用对象(callable),FastAPI 就会像解析路径操作函数的参数一样去解析它。本指南以本仓库官方教程文档 classes-as-dependencies.md 为主线,演示如何把前文依赖示例中的 dict 返回改为一个携带 __init__ 参数的类,从而让编辑器获得完整的代码补全与类型检查能力,并讲解 Depends() 无参简写背后的解析规则。读完你将掌握"类即依赖"的声明方式、类型注解与 Depends 各自承担的职责,以及 FastAPI 底层对 callable 的统一处理机制。

一个 dict 能做什么:从上一个示例说起

在依赖注入教程的上一部分(对应 tutorial001_an_py310.py),我们的依赖函数"dependable"返回的是一个 dict

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons

于是 read_items / read_users 路径操作函数参数 commons 中拿到的就是一个 dict。问题随之而来:编辑器无法给 dict 提供太多支持(比如补全),因为它根本不知道这个字典里有哪些键、每个键的值是什么类型。commons["q"] 这种写法既没有补全提示,也无法做静态类型检查。

我们可以做得更好——用一个真正的 Python 类来替代。

什么构成一个依赖:关键在"可调用对象"

到目前为止,教程中出现的依赖都是用函数声明的:

async def common_parameters(...):
    ...

但函数不是声明依赖的唯一方式(虽然它可能是最常见的一种)。决定性的因素只有一个:依赖应当是一个"可调用对象"(callable)

在 Python 里,"callable"指任何可以像函数一样被"调用"的东西。只要存在一个对象 something(它可能不是函数),而你能够这样执行它:

something()

或者:

something(some_argument, some_keyword_argument="foo")

那么 something 就是一个可调用对象。

类也是可调用对象

细想一下:创建一个 Python 类的实例,用的正是同一套调用语法:

class Cat:
    def __init__(self, name: str):
        self.name = name


fluffy = Cat(name="Mr Fluffy")

这里 fluffyCat 类的一个实例;而要制造出 fluffy,你正是在"调用" Cat。也就是说,Python 类天然就是一个可调用对象

因此,在 FastAPI 中可以直接把 Python 类当作依赖来用。FastAPI 实际校验的只有两件事:

  1. 传进来的东西是否是一个"callable"(函数、类或任何其它可调用对象);
  2. 它声明了哪些参数。

如果你把一个 callable 作为依赖传给 FastAPI,FastAPI 会分析该 callable 的参数,并按与路径操作函数参数完全相同的方式去处理它们——包括嵌套子依赖。这个规则同样适用于完全没有参数的 callable,正如无参数的路径操作函数一样。

从源码看,FastAPI 在 fastapi/dependencies/utils.py 中的 get_dependant()(第 271 行起)与 analyze_param()(第 381 行起)完成这套解析:它们对 dependency 的 call 统一分析签名、提取子依赖并逐参数处理,而并不关心这个 callable 到底是 def 函数、class 还是别的可调用形式。get_dependant 中还会通过 _is_async_gen_callable_is_gen_callable 等工具判定依赖属于普通协程、同步函数还是生成器变体,见 fastapi/dependencies/utils.py 对"parameter-less dependency must have a callable dependency"的断言。

用类声明依赖:__init__ 即依赖的参数

现在把上面的依赖 common_parameters 改写成类 CommonQueryParams。完整示例见 tutorial002_an_py310.py

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

请注意用来创建实例的 __init__ 方法:

def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):

它的参数与之前的函数 common_parameters 完全一致。这些参数正是 FastAPI 用来"求解"依赖的输入,也就是说,FastAPI 会从 __init__ 的签名中提取查询参数的声明。两种写法最终得到的是完全相同的三件事:

  • 一个可选的 q 查询参数,类型为 str
  • 一个 skip 查询参数,类型为 int,默认值 0
  • 一个 limit 查询参数,类型为 int,默认值 100

两种写法下,这些数据都会被同样地转换类型、校验、写入 OpenAPI schema 文档,等等。唯一区别在于:函数版本把它们塞进一个 dict,类版本则把它们绑定为实例属性,进而在路径操作函数里可以直接通过 commons.qcommons.skip 访问。

使用这个类依赖

现在即可用这个类声明依赖:

@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):

FastAPI 会"调用" CommonQueryParams 类、创建一个该类的实例,并把实例作为参数 commons 传入你的函数。__init__ 中写明的每个参数都会从请求中解析——qskiplimit 从查询字符串而来,并在实例化前完成类型转换与校验。

这样,路由处理函数拿到的是一个具有真实类型 CommonQueryParams 的对象,编辑器能基于属性类型提供 commons.qcommons.skipcommons.limit 的补全,拼错属性名也会被静态检查捕获。

类型注解与 Depends 各司其职

请观察上面代码里 CommonQueryParams 出现了两次:

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

(Python 3.10+ 非 Annotated 写法为 commons: CommonQueryParams = Depends(CommonQueryParams);教程的 tip 建议尽可能使用 Annotated 版本。)

  • 第二个 CommonQueryParams,即 Depends(CommonQueryParams) 里的那个,才是 FastAPI 真正用来识别依赖的:它会从这一个里提取声明的参数,最终也是调用它。
  • 第一个 CommonQueryParams(类型注解位置的那个)对 FastAPI 没有任何特殊含义。FastAPI 不会用它做数据转换、校验等工作(那由 Depends(CommonQueryParams) 负责)。

因此,你甚至可以只写类型为 Any 的版本(见 tutorial003_an_py310.py):

from typing import Annotated, Any

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

(非 Annotated 版本对应 commons = Depends(CommonQueryParams)。)

仍然强烈建议声明类型——这样做你的编辑器才能知道传入参数 commons 的是什么,进而给你提供代码补全与类型检查:

在编辑器中为 CommonsQueryParams 类的依赖实例提供类型提示与自动补全的界面截图

快捷方式:Depends() 免重复声明

上面的写法仍然有重复代码:CommonQueryParams 要手写两遍。

FastAPI 为这类依赖特指某个将被调用并创建实例的类的情况提供了一种快捷方式。与其写:

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

不如写:

commons: Annotated[CommonQueryParams, Depends()]

即把依赖声明为参数类型本身,并用不带任何参数的 Depends(),FastAPI 会从类型注解推断出该实例化哪个类。非 Annotated 版本对应 commons: CommonQueryParams = Depends()

完整示例见 tutorial004_an_py310.py

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.skip : commons.skip + commons.limit]
    response.update({"items": items})
    return response

如果这个技巧让你觉得反而更绕,那完全可以忽略它——它不是必须的,只是一种用来减少代码重复的简写。

需要注意它的适用边界:只有当依赖是"FastAPI 将会调用它来创建该类实例本身"的具体类时,Depends() 无参写法才成立。若依赖是函数、或者需要在 Depends(...) 里传入其它对象/参数,仍然需要显式写出依赖体。

运行验证:三种写法行为完全一致

本仓库的官方测试覆盖了这三种写法的等价性。在 tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py 中,tutorial002tutorial003tutorial004(含 _py310_an_py310 变体)被统一收集为一个参数化的 client fixture,然后对同一组请求断言完全相同的响应。例如:

  • 请求 /items(无参数)返回完整列表 FooBarBaz
  • 请求 /items?q=foo 在响应中附带 "q": "foo"
  • 请求 /items?q=foo&skip=1 从第二项开始返回切片;
  • 请求 /items?q=bar&limit=2 只取前两条(可在该文件后续部分看到)。

测试文件第 10-25 行通过 pytest 的参数化 fixture 把这 6 个模块(tutorial002_py310tutorial002_an_py310tutorial003_py310tutorial003_an_py310tutorial004_py310tutorial004_an_py310)放进同一套断言,从测试层面直接印证:函数式 dict 依赖、Annotated[Any, Depends(类)] 以及 Depends() 简写,解析出的查询参数与响应结构完全一致。

小结

围绕"类作为依赖"这一主题,可以提炼出四条可直接使用的经验:

  1. 依赖的本质是 callable。FastAPI 只关心"能不能调用"以及"参数是什么",函数、类乃至任意实现了 __call__ 的对象都能成为依赖。
  2. 类的 __init__ 参数就是依赖参数。把公共查询参数(或其它注入数据)放进 __init__,FastAPI 会在每次请求时实例化该类并完成参数解析、转换、校验与 OpenAPI 文档化。
  3. 类型注解 ≠ 依赖体Depends(...) 中传入的对象才决定依赖逻辑;类型注解仅供编辑器与静态检查使用,因此应尽量声明真实类型而非 Any
  4. Depends() 是"类即依赖"的简写。当依赖正是那个要被调用实例化的类时,可以省去在 Depends 里重复类名,但若依赖是函数等其它 callable,仍需显式书写。

相关文档与源码可继续查阅:依赖注入教程目录、本页依赖的核心解析实现 fastapi/dependencies/utils.py 以及等价性测试 test_tutorial002_tutorial003_tutorial004.py

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

项目优选

收起
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