FastAPI Depends 缓存行为
缓存范围:单请求作用域,默认开启
use_cache=True
同一次HTTP请求生命周期内,同一个依赖函数只会执行1次;多处复用直接拿缓存结果,不会重复调用函数。
⚠️跨请求之间不缓存,新请求全部重新执行。
示例直观感受
def get_db():
print("创建db会话")
return "session_xxx"
def get_user(db = Depends(get_db)):
return f"user:{db}"
def get_setting(db = Depends(get_db)):
return f"setting:{db}"
@app.get("/demo")
async def demo(
u = Depends(get_user),
s = Depends(get_setting),
):
return {"u":u, "s":s}
一次请求访问接口,控制台只会打印一次创建db会话。
get_user、get_setting拿到的是完全同一个返回对象。
底层原理:
FastAPI处理单个请求时,内部维护一个请求内的缓存字典,key是依赖函数本身对象,value是执行后的返回结果。
解析依赖树的时候,先查缓存字典:
- 如果已经存在,直接取缓存返回,不执行函数;
- 不存在,执行依赖函数,把结果存入缓存字典。
缓存字典是每个请求独立一份,请求结束直接丢弃,不会跨请求复用。
use_cache 参数
Depends(func, use_cache=False) 关闭缓存,每次遇到这个依赖,强制重新执行函数,不走缓存。
def rand():
import random
return random.randint(1,100)
@app.get("/r")
async def r(
a = Depends(rand),
b = Depends(rand, use_cache=False),
):
# a来自缓存,b强制重新执行;a、b值大概率不一样
return {"a":a, "b":b}
适用场景:依赖每次调用都需要产生全新结果,不能复用上一次返回值。
yield生成器依赖的缓存
def get_db():
print("创建连接")
sess = create_session()
try:
yield sess
finally:
print("关闭连接")
sess.close()
因为Depends缓存,同请求无论多少地方引用Depends(get_db):
- 只会执行一遍函数,执行到
yield,返回session; - 请求全部处理完毕之后,执行finally关闭会话,只会关闭一次。
如果关闭缓存
use_cache=False,会多次执行get_db,多次创建、多次关闭会话,数据库连接会乱掉。数据库会话依赖绝对不能关闭use_cache。
scope 参数(0.121.0+新增,yield依赖)
scope="request"【默认】
yield之后清理逻辑,整个请求全部处理完成、响应准备完毕之后才执行finally。scope="function"
接口视图函数执行完成,立刻执行yield后面finally,在返回response之前就释放资源。
Depends(get_db, scope="function")
重要坑点(高频踩坑)
-
缓存key是函数对象本身,不是函数名字符串。
就算两个函数逻辑一模一样,是两个不同函数对象,不会共享缓存。 -
缓存只在同一个请求内部生效;不同请求完全隔离。
❌不要误以为Depends会全局缓存对象,千万不要在Depends里面放全局共享数据库连接。
- 依赖内部抛出异常,异常也会被缓存。同请求后续再次命中该依赖,会直接抛出同一个异常,不会重新执行函数。
def bad_dep():
print("执行bad_dep")
raise ValueError("boom")
@app.get("/test")
async def test(
x=Depends(bad_dep),
y=Depends(bad_dep)
):
return {}
只会打印一次执行bad_dep,两次都抛出异常。
- 缓存只针对Depends解析树;手动直接调用函数不受Depends缓存管控。
@app.get("/xxx")
async def xxx(db=Depends(get_db)):
get_db() # 这里是你手动调用,不走Depends缓存,会再次执行
- HTTP中间件层面拿不到这个Depends缓存字典;Depends缓存是路由内部依赖解析阶段才构建。
手写极简模拟Depends缓存逻辑
# 每个请求会生成一个独立cache字典
request_cache = {}
def resolve_dep(func):
if func in request_cache:
return request_cache[func]
res = func()
request_cache[func] = res
return res
每个请求到来,新建空字典;请求结束直接丢弃字典。
和你前面知识串起来
- Depends发生在HTTP洋葱中间件执行完成之后、路由内部解析阶段。
- 每个请求有自己独立的依赖缓存字典,属于请求生命周期内的内存容器。
- 这也是语法糖,底层就是框架维护字典,解析依赖树时先查缓存再执行函数。
一句话总结
Depends默认开启请求内缓存:同一个HTTP请求中,同一个依赖函数只运行一次,多处复用直接拿缓存结果;缓存只存活于单次请求,请求结束销毁;use_cache=False可以强制每次重新执行。数据库会话这类资源依赖,必须依赖这个缓存保证只创建一次、关闭一次。

283

被折叠的 条评论
为什么被折叠?



