依赖缓存
本教程共 50 篇 · 第 24 篇 · 更新于 2026-08-12 · 约 6 分钟阅读
本节目标:学会在路径操作装饰器里声明”只想要副作用、不接收返回值”的依赖,并理解 FastAPI 对同一请求内重复依赖的缓存机制,以及何时关掉它。
前面两章,依赖的返回值都通过路径操作函数的参数接住。但有些依赖你想要的不是它的返回值,而是它”做事”的副作用。比如校验请求头、检查权限,校验完直接放行,路径操作里根本用不到那个值。
这一章讲两种相关技巧:装饰器的 dependencies 参数,以及依赖的缓存行为。
24-1 不需要返回值的依赖
有些依赖只要”被执行过”就行。比如一个检查请求头 X-Token 是否合法的依赖,合法就放行,不合法就报错。它的返回值路径操作函数压根用不上。
如果还按之前的方式写在参数里,编辑器会提示”这个参数没用到”,看着别扭。FastAPI 允许你把这类依赖放到路径操作装饰器上。
24-2 装饰器的 dependencies 参数
路径操作装饰器(@app.get(...) 等)接收一个 dependencies 参数,它是一组 Depends() 的列表。这些依赖照样会被执行、被解决,但它们的值不会传进路径操作函数。
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
async def verify_token(x_token: Annotated[str, Header()]) -> None:
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token 头无效")
async def verify_key(x_key: Annotated[str, Header()]) -> None:
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key 头无效")
@app.get(
"/items/",
dependencies=[Depends(verify_token), Depends(verify_key)],
)
async def read_items() -> dict:
return [{"item": "Foo"}, {"item": "Bar"}]
这里 verify_token 和 verify_key 都在装饰器里声明。每次访问 /items/,FastAPI 都会先执行它们:头不对就抛 400,对了就放行进入 read_items。
因为依赖写在装饰器上,路径操作函数 read_items 的签名干干净净,没有”用不到的参数”。编辑器、新同事都不会误以为那个参数还有用。
Note示例里的
X-Token、X-Key是随便造的自定义请求头,只为演示。真实项目做鉴权时,建议直接用 FastAPI 内置的 Security 工具(后面安全章节会讲),收益更大。
24-3 装饰器依赖能用普通依赖
装饰器里的依赖,和你平时用的依赖函数是同一套东西。它们能声明请求参数(如 Header)、能挂子依赖、能抛异常、也能返回值(只是返回值不用而已)。
这意味着你完全可以把一个已经在别处用、且有返回值的依赖,也挂到装饰器上。它照样被执行,只是这次的返回值被丢弃。
24-4 同一组接口的依赖
当一个大项目拆成多个文件、多个接口属于同一组时,可以给整组接口统一加 dependencies。等讲到”大型应用多文件”结构时,你会看到怎么给一组路由一次性声明公共依赖,而不用每条都写一遍。
这就是装饰器依赖的延伸价值:把”横切”的逻辑(日志、限流、公共校验)集中声明。
24-5 依赖的缓存机制是什么
现在讲另一个重要机制:同一请求内,同一个依赖默认只执行一次。
想象你有两条子依赖都依赖同一个底层的 get_value。FastAPI 在解决依赖树时,发现 get_value 被多处需要,它不会傻乎乎地调两遍,而是调用一次、把结果缓存起来,再分发给所有需要它的地方。
这在官方文档里就叫 cache(缓存)。它针对的是”同一个请求”,不是跨请求的全局缓存,别和 Redis 那种搞混。
24-6 缓存带来的好处
缓存最直接的好处是省性能。典型的例子是”解析当前用户”:
from typing import Annotated
from fastapi import Depends
app = FastAPI()
async def get_current_user(token: Annotated[str, Header()]) -> dict:
# 假设这里要去数据库或调鉴权服务查用户
user = {"username": "alice", "token": token}
return user
如果 get_current_user 被 read_profile、update_profile 等多个子依赖共同需要,没有缓存就要查库好几遍。有了请求级缓存,同一请求里只查一次,后面的依赖直接复用结果。
Tip凡是”计算贵、但同一请求内结果不变”的依赖,都该享受这个缓存。数据库连接、配置加载、权限解析,都是典型收益场景。
24-7 用 use_cache 关掉缓存
有些情况你偏偏想让依赖每次都重新执行,不要复用缓存值。这时给 Depends 传 use_cache=False:
from typing import Annotated
from fastapi import Depends
async def get_value() -> str:
return "某个可能每次都变的值"
async def needy_dependency(
fresh_value: Annotated[str, Depends(get_value, use_cache=False)],
) -> dict:
return {"fresh_value": fresh_value}
use_cache=False 告诉 FastAPI:别缓存这个依赖,同一请求里每用到一次就真的再调一次。默认是 use_cache=True,也就是会缓存。
绝大多数场景用默认即可。只有当你确信依赖的每次结果都必须是最新、且缓存会出错时,才显式关掉它。
24-8 缓存与子依赖配合
缓存对子依赖尤其划算。前面第 23 章讲过子依赖链:一个 query_extractor 被多个上层依赖共用。在同一个请求里,FastAPI 只调用一次 query_extractor,把结果喂给所有需要它的上层。
你写代码时不用操心”会不会重复算”,框架已经替你做了优化。你要做的只是正常声明依赖关系,缓存自动生效。
24-9 什么时候该用哪种写法
简单总结一下选择:
- 路径操作里要用依赖的返回值 → 写在路径操作函数参数里,用
Depends(...) - 路径操作里不关心返回值、只要它执行过 → 写在装饰器
dependencies=[...]里 - 依赖结果在同一请求内不该变、且计算贵 → 用默认缓存(什么都不用写)
- 依赖结果必须每次都重新算 → 加
use_cache=False
这两类技巧都属于依赖注入的”基础设施”,配合前面的类依赖、子依赖,能搭出很清晰的结构。
24-10 小结
装饰器的 dependencies 参数用来声明”只执行、不取值”的依赖,常做校验和副作用,让路径操作签名更干净。同一请求内,FastAPI 默认把重复依赖的结果缓存起来,只算一次,省性能。需要每次都重新执行时,用 use_cache=False 关掉缓存。下一章我们看依赖还能用 yield 管理资源。