首页 / FastAPI 入门教程 / 类作为依赖与子依赖

FastAPI 入门教程

类作为依赖与子依赖

本教程共 50 篇 · 第 23 篇 · 更新于 2026-08-12 · 约 8 分钟阅读

FastAPIFastAPI 入门教程类作为依赖子依赖Depends可调用对象依赖链实例化

本节目标:学会用 Python 类当依赖,理解类实例能持有自己的状态,并掌握”依赖的依赖”(子依赖)如何层层调用。

上一章我们用函数当依赖,拿到了一个字典。字典有个麻烦:编辑器不知道里面有哪些键、值是什么类型,补全和报错都帮不上忙。

FastAPI 不限制你只用函数。任何”可调用对象”都能当依赖,最常见的就是类。这一章我们换个思路,用类来装依赖的数据,再看看依赖还能套依赖。

23-1 返回字典的局限

先回顾上一章的依赖,它返回字典:

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,
) -> dict:
    return {"q": q, "skip": skip, "limit": limit}


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

路径操作里的 commons 是个 dict,编辑器只能当它是不明结构的字典。你想写 commons.q 时,编辑器不会提示,写错了也不会报警。我们要做得更好。

23-2 什么样的对象能当依赖

关键概念是”可调用对象(callable)“。在 Python 里,只要你能像函数一样去”调用”它,它就是可调用对象:

something()
something(some_argument, some_keyword_argument="foo")

函数能调用,这没问题。但你可能没注意到:创建类的实例,用的也是调用语法。看这段:

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


fluffy = Cat(name="Mr Fluffy")

这里 Cat(name="Mr Fluffy") 就是在”调用” Cat 这个类。所以类本身也是可调用对象。

FastAPI 判断依赖时,看的是”它是不是可调用、它有哪些参数”。类符合这个条件,于是类也能当依赖。

23-3 用类改写依赖

我们把上一章的 common_parameters 函数,改成 CommonQueryParams 类。__init__ 的参数就是原本依赖的参数:

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,
    ) -> None:
        self.q = q
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(
    commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)],
) -> dict:
    response: dict = {}
    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 会”调用” CommonQueryParams 这个类,也就是执行它的 __init__,创建一个实例。这个实例被送进路径操作函数的 commons 参数。

现在 commons 是明确的 CommonQueryParams 类型,你可以写 commons.qcommons.skip,编辑器能补全、能检查类型。相比字典,体验好太多。

Note

FastAPI 会解析类的 __init__ 参数,和解析路径操作函数参数一模一样:类型转换、校验、写进 OpenAPI 文档,一件不落。

23-4 类型注解与 Depends 写了两遍

注意上面这行写了两次 CommonQueryParams

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

第二个 Depends(CommonQueryParams) 才是给 FastAPI 看的,它从中提取参数、决定怎么调用。第一个 CommonQueryParams 只是类型注解,给编辑器看的,FastAPI 不会拿它做数据校验。

所以理论上你可以把注解写成 Any,甚至写成下面这样也完全能跑:

commons: Annotated[object, Depends(CommonQueryParams)]

但强烈建议保留真实类型 CommonQueryParams,因为那样编辑器才知道 commons 是什么,补全和类型检查才有效。

23-5 类内可以持有状态

类最大的好处,是实例能”持有状态”。上面 CommonQueryParamsqskiplimit 存成了自己的属性。路径操作函数拿到的就是带着这些值的对象。

更进一步,依赖类还能在构造时做更多初始化。比如记录创建时间、预计算某些值、建立连接:

from datetime import datetime

from typing import Annotated

from fastapi import Depends


class RequestContext:
    def __init__(self, q: str | None = None) -> None:
        self.q = q
        self.received_at = datetime.now()

这样每次请求进来,RequestContext 实例都带着自己的 received_at。这就是”类内持有状态”——每个请求一份独立的实例,互不干扰。

23-6 Depends() 快捷写法

当一个依赖正好是”用类创建自身实例”时,FastAPI 提供快捷写法:把类写在类型注解里,依赖写空的 Depends()

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

FastAPI 看到 Depends() 没给参数,就会用参数类型 CommonQueryParams 当作依赖。效果跟 Depends(CommonQueryParams) 一样,但少写一遍类名。

Tip

如果这写法让你觉得更绕,直接忽略它也没关系。它只是个减少重复的快捷键,不是必须的。

23-7 什么是子依赖

依赖还能依赖别的依赖,这就是子依赖。比如先写一个最简单的 query_extractor,从查询参数里取 q

from typing import Annotated

from fastapi import Cookie, Depends, FastAPI

app = FastAPI()


def query_extractor(q: str | None = None) -> str | None:
    return q

再写一个 query_or_cookie_extractor,它自己既是依赖,又依赖上面的 query_extractor

def query_or_cookie_extractor(
    q: Annotated[str | None, Depends(query_extractor)],
    last_query: Annotated[str | None, Cookie(None)] = None,
) -> str | None:
    if not q:
        return last_query
    return q

这里的 q 来自子依赖 query_extractor 的返回值;last_query 来自 Cookie。如果用户没传 q,就用上次存进 Cookie 的值。

23-8 使用带子依赖的依赖

路径操作里只写一个依赖 query_or_cookie_extractor,FastAPI 会自动先把里面的 query_extractor 解决掉:

@app.get("/items/")
async def read_query(
    query_or_default: Annotated[str | None, Depends(query_or_cookie_extractor)],
) -> dict:
    return {"q_or_cookie": query_or_default}

你只声明了一个依赖,但 FastAPI 知道得先调用 query_extractor 拿到 q,再把它传给 query_or_cookie_extractor,最后才执行路径操作。

依赖链可以很深,FastAPI 会帮你把整棵依赖树都解出来。顺序是:最底层的子依赖先跑,结果一层层往上送。

23-9 子依赖的实际意义

子依赖看似绕,用处很大。典型场景是权限分级:一个 current_user 依赖解析出当前登录用户;active_user 依赖它、顺便检查账号是否激活;admin_user 再依赖 active_user、检查是不是管理员。

这样每个接口只要挂自己需要的最高层依赖,下面的检查自动全做了。等讲到安全那一章,你会看到它省下多少代码。

23-10 小结

依赖不限于函数,类也是可调用对象,能当依赖。FastAPI 调用类的 __init__ 生成实例,实例能持有自己的状态,且编辑器补全友好。

依赖还能套依赖,形成子依赖链。FastAPI 自动按从底向上的顺序解决整棵树,把每层结果注入上层。下一章我们讲依赖的缓存,以及一种”不需要返回值、只想要副作用”的依赖写法。