Skip to content

单例与依赖作用域

模块单例、lru_cache、FastAPI 依赖的缓存语义与作用域。

Updated View as Markdown
For humans

单例与依赖作用域

单例模式的本意是“全局唯一实例”。Python 里模块就是单例,但单例的真正考点在作用域:实例活多久、谁共享。FastAPI 的依赖系统把这个问题具体化了。

模块单例

# config.py 模块级对象
settings = load_settings()

import config 多次执行,模块只加载一次(sys.modules 缓存),config.settings 全局唯一。这就是 Python 的单例:不需要类,不需要 getInstance

传统 Java 式单例类(__new__ 控制实例化)在 Python 里是反模式:全局状态难测试、并发初始化有竞态、违背显式优于隐式。

lru_cache:进程级单例

需要“惰性初始化 + 全局复用”时,@lru_cache 是惯用方案:

from functools import lru_cache

@lru_cache
def get_db_pool():
    return create_pool()  # 首次调用创建, 之后复用
  • 缓存的是返回值,天然单例
  • 无参数或参数可哈希时才有效;需要按参数区分实例(如多租户)时传参数即可
  • 测试隔离:get_db_pool.cache_clear() 重置

FastAPI 依赖的作用域

FastAPI 的 Depends 默认每次请求重新执行依赖函数(请求级,同一请求内多次引用复用一次),这常被误解:

依赖默认请求级, lru_cache 提升为进程级

三种作用域的写法:

作用域 写法 生命周期
请求级(默认) 普通依赖函数 每次请求新建
应用级(单例) 依赖函数加 @lru_cache 进程内唯一
请求级带清理 yield 依赖 请求结束执行清理(关会话)
@lru_cache
def get_settings():          # 应用级: 全局读一次配置
    return Settings()

def get_db():                # 请求级: 每次请求一个会话
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

与 Spring 的对比

维度 FastAPI Spring
单例 lru_cache / 模块对象 默认 singleton
请求级 依赖默认行为 request 作用域
会话级 手动实现(request 上下文) session 作用域
作用域声明 无专门语法,靠缓存装饰器 @Scope 注解

面试结论:FastAPI 没有内置作用域语法,靠 Python 特性拼装;Spring 有完整作用域体系。这是框架设计取向差异。

面试追问

  1. Python 的单例怎么写? 模块级对象或 lru_cache。类加 new 是 Java 思维,不惯用
  2. FastAPI 依赖每次请求都执行吗? 默认是。要复用实例就 lru_cache 包依赖函数
  3. yield 依赖是什么? 请求结束执行 finally 清理资源(数据库会话、连接)。FastAPI 的资源管理惯用法
  4. 单例有什么问题? 全局状态难测试、并发初始化竞态。需要时可注入替换,别到处直接引用
  5. 怎么在测试里重置单例? lru_cache 用 cache_clear(),模块对象用 monkeypatch 替换
Navigation

Type to search…

↑↓ navigate↵ selectEsc close