类作为依赖与子依赖
本教程共 50 篇 · 第 23 篇 · 更新于 2026-08-12 · 约 8 分钟阅读
本节目标:学会用 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.q、commons.skip,编辑器能补全、能检查类型。相比字典,体验好太多。
NoteFastAPI 会解析类的
__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 类内可以持有状态
类最大的好处,是实例能”持有状态”。上面 CommonQueryParams 把 q、skip、limit 存成了自己的属性。路径操作函数拿到的就是带着这些值的对象。
更进一步,依赖类还能在构造时做更多初始化。比如记录创建时间、预计算某些值、建立连接:
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 自动按从底向上的顺序解决整棵树,把每层结果注入上层。下一章我们讲依赖的缓存,以及一种”不需要返回值、只想要副作用”的依赖写法。