找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2577

积分

0

好友

341

主题
发表于 1 小时前 | 查看: 1| 回复: 0

依赖注入是 FastAPI 的核心特性之一,它能极大提升代码的复用性与可维护性。很多人初学 FastAPI 依赖注入,可能只接触了最基本的函数依赖。实际上,FastAPI 的依赖项不仅限于此。本文将带你系统性地掌握 FastAPI 依赖项的两种类型(函数与类)以及两种作用域(请求级与应用级),配合代码示例,帮你构建更优雅、高效的 Web 服务。

先补个小知识:类和实例

在深入讲解依赖类型之前,我们先快速回顾一下 Python 中类和实例的基本概念。

类和实例的知识卡片
类和实例的概念区别示意图

简而言之,类定义了对象的模板(属性和方法),而实例是根据这个模板创建的具体对象,每个实例拥有自己独立的状态。

第一种类型:函数作为依赖项

函数依赖是最常见、最直接的用法。它非常简单:每次请求时,FastAPI 都会重新执行一遍这个函数。

来看一个具体的代码示例:

from fastapi import FastAPI, Depends

app = FastAPI()

def get_query_params(
  q: str = None,
  limit: int = 10
  ):
    return {"q": q, "limit": limit}

@app.get("/items/")
async def read_items(
  params: dict =
      Depends(get_query_params)
  ):
  return params

上面例子中的 get_query_params 就是一个函数依赖。它的特点是“无状态”,每次调用都是全新的开始。

函数依赖的特点与适用场景

第二种类型:类作为依赖项

类依赖比函数依赖更强大,因为它可以保存状态,适用于更复杂的业务场景。FastAPI 支持两种类依赖的写法。

写法一:直接用类

我们可以直接将一个类声明为依赖项,FastAPI 会自动实例化它。

from fastapi import FastAPI, Depends

app = FastAPI()

class QueryParams:
  def __init__(self, q: str = None,
        limit: int = 10):
    self.q = q
    self.limit = limit

@app.get("/items/")
async def read_items(params:
     QueryParams = Depends(QueryParams)):
    return {
    "q": params.q,
    "limit": params.limit
  }

写法二:用 __call__ 方法

通过定义 __call__ 方法,我们可以让类的实例本身变得可调用,从而作为依赖项。

from fastapi import FastAPI, Depends

app = FastAPI()

class Counter:
   def __init__(self):
      self.count = 0
   def __call__(self):
     self.count += 1
     return self.count

counter = Counter()

@app.get("/count/")
async def get_count(
  current_count: int = Depends(counter)
  ):
     return {"count": current_count}

思考一下:为什么这个例子中,每次调用 /count/ 接口,返回的数字都会累计+1?

答案是,我们在外部创建了一个 Counter 类的实例 counter,并将其作为依赖项。由于这个实例在整个应用生命周期内是唯一的,其内部的 count 状态得以保留。

类依赖的特点与适用场景

如何选择函数依赖还是类依赖?关键在于你是否需要保存状态。这里有一个核心口诀可以帮助你决策:
选择依赖类型与作用域的核心口诀

两种作用域:请求级 vs 应用级

作用域决定了依赖项实例的生命周期。我们可以用一个简单的比喻来理解:

  • 请求级作用域 = 酒店房间里的一次性牙刷毛巾。每次新客人入住(每次请求),都会提供全新的一套。
  • 应用级作用域 = 酒店大堂的沙发和电梯。整个酒店只有一份,所有客人共用。

请求级作用域(默认)

这是 FastAPI 的默认行为。每次请求到来时,依赖项都会被重新创建和初始化。

from fastapi import FastAPI, Depends

app = FastAPI()

class RequestCounter:
   def __init__(self):
     self.count = 0
   def __call__(self):
     self.count += 1
     return self.count

@app.get("/request-count/")
async def get_request_count(
  count: int = Depends(
              RequestCounter)):
  return {"count": count}

运行上面的代码,你会发现每次请求 /request-count/ 返回的都是 1。因为每次请求 FastAPI 都创建了一个新的 RequestCounter 实例,计数器每次都从0开始加1。

应用级作用域

有些资源(如数据库连接池、全局配置)我们希望在应用整个生命周期内只初始化一次,所有请求共享。这就要用到应用级作用域。

方法一:用 lru_cache 装饰类

利用 functools.lru_cache 装饰器可以缓存类的实例。

from fastapi import FastAPI, Depends
from functools import lru_cache

app = FastAPI()

@lru_cache()
class AppCounter:
   def __init__(self):
     self.count = 0
   def __call__(self):
      self.count += 1
      return self.count

@app.get("/app-count/")
async def get_app_count(
          count: int =
              Depends(AppCounter)):
    return {"count": count}

方法二:在外部创建实例

更直接的方式是在模块级别预先创建好实例。

from fastapi import FastAPI, Depends

app = FastAPI()

class AppCounter:
  def __init__(self):
   self.count = 0
  def __call__(self):
    self.count += 1
    return self.count

app_counter = AppCounter()

@app.get("/app-count/")
async def get_app_count(
    count: int =
       Depends(app_counter)):
     return {"count": count}

使用以上两种方法,每次请求 /app-count/ 返回的数字都会在上一次的基础上递增。这是因为所有请求都依赖于同一个 AppCounter (或 app_counter) 实例。

依赖项的嵌套调用

依赖项可以像搭积木一样层层嵌套,FastAPI 会自动解析并按照正确的顺序执行它们。这在实际开发中非常有用,例如构建一个需要验证 API Key、获取用户信息、再建立数据库连接的接口。

from fastapi import FastAPI, Depends,
       HTTPException

app = FastAPI()

def get_api_key(api_key: str):
  if api_key != "secret":
    raise HTTPException(status_code=401,
       detail="Invalid API Key")
    return api_key

def get_current_user(api_key: str
      = Depends(get_api_key)):
      return {"username": "admin",
          "api_key": api_key)

def get_db_connection(current_user:
    dict = Depends(get_current_user)):
       return {"db": "connection",
         "user": current_user}

@app.get("/protected/")
async def protected_route(db =
        Depends(get_db_connection)):
  return {"message": "You are in!", "db": db}

依赖链解析顺序
FastAPI 会先解析 get_db_connection,发现它依赖 get_current_user;进而发现 get_current_user 又依赖 get_api_key。最终的执行顺序是:get_api_key -> get_current_user -> get_db_connection -> protected_route
FastAPI依赖链的解析流程图

常见误区与避坑指南

❌ 误区一:在类依赖中错误使用实例变量

如果你希望类依赖保存状态,但写法上却导致每次请求都创建新类,状态将无法保留。

错误写法

@app.get("/count/")
async def get_count(count: int
      = Depends(Counter)):
         ...

这种写法中,Depends(Counter) 会导致每个请求都新建一个 Counter 类,count 无法累加。

正确写法

counter = Counter()
@app.get("/count/")
async def get_count(count:
    int = Depends(counter)):

预先创建实例 counter 并传入 Depends,才能保证状态持久化。

❌ 误区二:过度使用应用级作用域

不是所有依赖都适合做成应用级。像数据库连接池、应用配置这类开销大、且无请求特异性的对象,适合应用级作用域。而用户身份、请求参数、事务上下文等与具体请求强相关的对象,必须使用请求级作用域,否则会导致数据混乱。

❌ 误区三:依赖链过长

虽然依赖可以嵌套,但应避免创建过长的依赖链(例如超过3层)。过长的依赖链会降低代码可读性,增加调试难度,形成“依赖地狱”。当逻辑变得复杂时,应考虑将部分依赖拆分或重构。

总结

掌握 FastAPI 依赖项的类型与作用域,是写出整洁、高效后端代码的关键。我们来回顾一下今天的核心要点:

  • 函数依赖:简单直接,无状态,适合参数提取、Token验证等一次性操作。
  • 类依赖:可以保存状态,适合需要多个方法协作或管理内部状态的复杂业务逻辑,这其实是一种常用的代码组织设计模式
  • 请求级作用域:默认行为,每次请求重新创建依赖实例,保证请求间的隔离性。
  • 应用级作用域:依赖实例全局唯一,适合初始化开销大、可共享的资源,如配置、连接池。
  • 嵌套依赖:允许构建清晰的依赖链,FastAPI 会自动管理解析与执行顺序。

简单来说,你可以这样记忆:类依赖像一位有记忆的服务员,记得你之前的喜好;函数依赖则像每次都是新来的服务员,一切从零开始。 合理运用它们,能让你的 FastAPI 项目结构更清晰,维护性更高。在云栈社区,你可以找到更多关于微服务架构与后端开发的深度讨论和实战案例,与广大开发者一起交流成长。




上一篇:TinyType:用动物化设计治愈碎片记录焦虑的移动应用体验
下一篇:RouteRAG:基于强化学习的可学习RAG检索策略,优化问答效率与准确性
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-31 05:45 , Processed in 0.667584 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表