首页
/ FastAPI 进阶实战:在路径操作装饰器中使用 dependencies 声明无需返回值的依赖

FastAPI 进阶实战:在路径操作装饰器中使用 dependencies 声明无需返回值的依赖

2026-09-06 11:57:28作者:劳婵绚Shirley

本篇技术指南讲解 FastAPI 依赖注入体系中一个易被忽视但非常实用的能力:当某个依赖只需要被执行/解决,而不需要把它的返回值传给你的路径操作函数时,可以将一组 Depends() 直接传入路径操作装饰器(如 @app.get(...))的 dependencies 参数。读完本文,你将掌握这种"无参数依赖"的正确写法、它与普通参数式依赖的行为差异、底层解析机制(Dependant 构建与 solve_dependencies 递归),以及官方测试用例验证的请求级行为(缺失 Header 返回 422、非法 Header 返回 400、合法 Header 返回 200)。

什么时候需要在装饰器中声明依赖

在日常开发中,你通常通过给路径操作函数添加带 Depends参数来声明依赖:

@app.get("/items/")
async def read_items(current_user: User = Depends(get_current_user)):
    ...

但有时会出现这样的场景:

  • 并不需要依赖的返回值,却必须让它执行(例如校验请求 Header、记录日志、刷新缓存);
  • 或者依赖本身不返回任何值(没有 return 语句),却仍然必须被运行/解决。

在这些情况下,与其在路径操作函数上声明一个实际用不到的参数,更干净的做法是:向 路径操作装饰器 传递一个由 Depends() 组成的 list,即 dependencies 参数。

给路径操作装饰器添加 dependencies

@app.get()@app.post() 等路径操作装饰器都接收一个可选的 dependencies 参数,它应该是一个 list,元素为 Depends()。官方示例完整代码如下(见 tutorial006_an_py310.py,另有非 Annotated 风格版本 tutorial006_py310.py):

from typing import Annotated

from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()


async def verify_token(x_token: Annotated[str, Header()]):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: Annotated[str, Header()]):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key


@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

这里的关键点:

  1. 声明位置:依赖没有出现在 read_items() 的函数签名里,而是声明在装饰器的 dependencies=[Depends(verify_token), Depends(verify_key)] 中。
  2. 执行语义:这些依赖和普通依赖以完全相同的方式被执行/解决(参数解析、校验、异常处理流程一致),区别在于它们的返回值(即使有)不会传递给你的路径操作函数。
  3. 返回值被丢弃:注意 verify_key 执行了 return x_key,但在本例中这个返回值根本用不到——这正是复用已有"带返回值的依赖"而不必改动它的前提。
  4. OpenAPI 依旧完整:官方测试 test_tutorial006.py 断言了 /openapi.json 的内容:x-tokenx-key 这两个 Header 参数仍然出现在 OpenAPI Schema 的 parameters 中(required: true, in: "header"),说明装饰器声明的依赖同样参与文档生成,客户端能清楚知道该接口需要哪些请求头。

提示:一些编辑器会检查"未使用的函数参数"并把它当作问题(error)标出。把依赖放到 dependencies 参数中,可以既保证依赖被执行,又不让编辑器/工具报错,同时避免误导新开发者——他们看到签名里一个未使用的参数,可能会误以为它是多余的代码。

注意:上面的示例使用两个虚构的自定义 Header X-KeyX-Token 来演示。在真实的安全场景中,使用 FastAPI 内置的安全工具体系收益更大,可参考 安全章节

依赖的参数要求、异常与返回值

装饰器中声明的依赖,可以直接复用你平时编写的那些依赖函数,行为规则完全一致:

依赖的参数要求

和普通依赖一样,依赖函数可以声明来自 请求 的参数(如 Header、Query、Path)或子依赖:

async def verify_token(x_token: Annotated[str, Header()]):
    ...

async def verify_key(x_key: Annotated[str, Header()]):
    ...

Header() 表示参数来自请求头(x_token 对应 HTTP 头 X-Token)。由于声明为 Annotated[str, Header()] 且没有默认值,该 Header 是必填的——缺失时框架在进入路径操作函数之前就会拒绝请求。这一点被官方测试直接验证(见 test_tutorial006.py):

  • 两个 Header 都缺失 → 返回 422detail 中包含两条校验错误,分别指向 loc: ["header", "x-token"]loc: ["header", "x-key"]msgField required
  • 只带错误的 X-Token(值不是 fake-super-secret-token)→ 返回 400detailX-Token header invalid
  • X-Token 正确但 X-Key 错误 → 返回 400detailX-Key header invalid
  • 两个 Header 都合法 → 返回 200,响应体为 [{"item": "Foo"}, {"item": "Bar"}]

这套测试同时参数化运行了两个版本的示例(tutorial006_py310tutorial006_an_py310),确认 Annotated 风格与传统 = Header() 风格行为一致。

抛出异常

依赖可以像普通依赖一样 raise 异常——示例中的 HTTPException(status_code=400, ...) 会直接短路请求处理并返回对应错误响应,路径操作函数根本不会被调用:

if x_token != "fake-super-secret-token":
    raise HTTPException(status_code=400, detail="X-Token header invalid")

返回值

依赖可以返回值,也可以不返回值——无论哪种情况,该值都不会被使用。因此你可以安全地复用一条"已经在别处使用、且带返回值"的依赖:即便在装饰器上下文中它的返回值没有消费者,它依然会被正常执行。示例中 verify_keyreturn x_key 就是这种情况。

源码剖析:装饰器里的 dependencies 是如何被执行的

结合源码可以看到这条能力背后的调用链,它解释了为什么"返回值被丢弃"与"参数校验仍然生效"同时成立。

第一步:装饰时构建无参数子依赖。 所有路径操作装饰器(get/post/put/delete 等,其定义与文档字符串可在 applications.pyrouting.py 中查阅,例如 APIRouter.get 的 docstring 明确写着 "A list of dependencies (using Depends()) to be applied to the current APIRouter" 并指向本篇文档主题)最终都会经过 routing.py 中的 _build_dependant_with_parameterless_dependencies()

def _build_dependant_with_parameterless_dependencies(
    *,
    path: str,
    call: Callable[..., Any],
    dependencies: Sequence[params.Depends],
) -> tuple[Dependant, list[ModelField], bool]:
    dependant = get_dependant(path=path, call=call, scope="function")
    for depends in dependencies[::-1]:
        dependant.dependencies.insert(
            0,
            get_parameterless_sub_dependant(depends=depends, path=path),
        )
    ...

可以看到,装饰器的每个 Depends 都会转换为一个 parameterless sub dependant(无参数子依赖)并插入路由的依赖列表中。转换逻辑在 dependencies/utils.pyget_parameterless_sub_dependant() 中:

def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
    assert callable(depends.dependency), (
        "A parameter-less dependency must have a callable dependency"
    )
    ...
    return get_dependant(
        path=path,
        call=depends.dependency,
        scope=depends.scope,
        own_oauth_scopes=own_oauth_scopes,
    )

从源码结构看有两点值得注意:

  • 装饰器里的依赖必须是可调用的依赖函数Depends(verify_token)),这也是为什么它天然适合"只要执行结果、不关心返回值"的场景;
  • Depends 上的 scope(如 request/function 级作用域)会被完整传递,因此装饰器声明的依赖同样支持按作用域缓存。

第二步:请求时递归求解,返回值按需丢弃。 请求到达时,路由会调用 dependencies/utils.pysolve_dependencies(),它遍历 dependant.dependencies 递归求解每一个子依赖:同步函数放入线程池执行、协程直接 await、生成器依赖通过 AsyncExitStack 管理 yield 前后资源。求解完成后,只有当 sub_dependant.name is not None 时,结果才会写入 values 字典供上层函数按参数名消费;而通过装饰器插入的这些子依赖没有绑定到路径操作函数的任何参数名上,因此即使 solved 有值,也不会注入到路径操作函数的调用参数中——这就是"返回值被丢弃"的机制根源。同时,若子依赖求解出错(solved_result.errors),错误会被 extend 汇总并向上传递,最终由框架统一转成 422 校验错误响应;而依赖内主动 raiseHTTPException 则走异常处理流程直接返回 400——与前面测试用例断言的响应一一对应。

第三步:Router 层级的复用。 如果你不是单个路由而是想给一组路由加同样的依赖,routing.pyAPIRouter 的构造与 include_router 逻辑会把父路由的 dependencies 合并进来(dependencies=[*parent_router.dependencies, *(dependencies or [])]),也就是说装饰器级、Router 级、应用级的依赖可以逐层叠加,这是下一节的伏笔。

给一组路径操作添加依赖

当你阅读完 更大的应用——多个文件 后,会学到如何把整个应用组织到多个文件中,以及如何为一组路径操作声明单一 dependencies 参数——通过 APIRouter(dependencies=[...])app.include_router(router, dependencies=[...]),让同一批依赖作用于该 Router 下的全部路由,而无需在每个路径操作装饰器里重复书写。

下一步:全局依赖

在本节基础上,接下来可以学习如何把依赖添加到整个 FastAPI 应用(FastAPI(dependencies=[...])),使其对应用内每一条路径操作生效,即"全局依赖"。

小结

  • 装饰器的 dependencies 参数接收 list[Depends()],依赖照常被执行/解决,但返回值不传递给路径操作函数;
  • 它让"只执行、不消费返回值"的依赖摆脱了未使用参数的尴尬,编辑器不再报"参数未使用",签名保持干净;
  • 依赖的参数要求(缺失 Header 触发 422)、抛出的异常(400 等自定义错误码)、以及 OpenAPI Schema 中的参数声明,均与普通依赖行为一致,且已被 tests/test_tutorial/test_dependencies/test_tutorial006.py 完整验证;
  • 底层由 _build_dependant_with_parameterless_dependencies 在装饰期构建无参数子依赖、由 solve_dependencies 在请求期递归求解,理解这一调用链有助于排查依赖缓存、作用域与错误传播相关问题。
登录后查看全文
热门项目推荐
相关项目推荐