FastAPI Depends 缓存行为

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_userget_setting拿到的是完全同一个返回对象。

底层原理:
FastAPI处理单个请求时,内部维护一个请求内的缓存字典,key是依赖函数本身对象,value是执行后的返回结果。
解析依赖树的时候,先查缓存字典:

  1. 如果已经存在,直接取缓存返回,不执行函数;
  2. 不存在,执行依赖函数,把结果存入缓存字典。

缓存字典是每个请求独立一份,请求结束直接丢弃,不会跨请求复用。

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依赖)

  1. scope="request"【默认】
    yield之后清理逻辑,整个请求全部处理完成、响应准备完毕之后才执行finally
  2. scope="function"
    接口视图函数执行完成,立刻执行yield后面finally,在返回response之前就释放资源
Depends(get_db, scope="function")

重要坑点(高频踩坑)

  1. 缓存key是函数对象本身,不是函数名字符串
    就算两个函数逻辑一模一样,是两个不同函数对象,不会共享缓存。

  2. 缓存只在同一个请求内部生效;不同请求完全隔离

❌不要误以为Depends会全局缓存对象,千万不要在Depends里面放全局共享数据库连接。

  1. 依赖内部抛出异常,异常也会被缓存。同请求后续再次命中该依赖,会直接抛出同一个异常,不会重新执行函数。
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,两次都抛出异常。

  1. 缓存只针对Depends解析树;手动直接调用函数不受Depends缓存管控
@app.get("/xxx")
async def xxx(db=Depends(get_db)):
    get_db() # 这里是你手动调用,不走Depends缓存,会再次执行
  1. 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

每个请求到来,新建空字典;请求结束直接丢弃字典。

和你前面知识串起来

  1. Depends发生在HTTP洋葱中间件执行完成之后、路由内部解析阶段
  2. 每个请求有自己独立的依赖缓存字典,属于请求生命周期内的内存容器。
  3. 这也是语法糖,底层就是框架维护字典,解析依赖树时先查缓存再执行函数。

一句话总结

Depends默认开启请求内缓存:同一个HTTP请求中,同一个依赖函数只运行一次,多处复用直接拿缓存结果;缓存只存活于单次请求,请求结束销毁;use_cache=False可以强制每次重新执行。数据库会话这类资源依赖,必须依赖这个缓存保证只创建一次、关闭一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草莓仙生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值