硬编码依赖导致测试和替换困难,因其强制耦合具体实现;应结合ABC(或Protocol)声明抽象接口,并通过构造函数注入依赖,使调用方只依赖“能做什么”,而非“是谁实现”。

为什么硬编码依赖会让测试和替换变困难
硬编码依赖指在类内部直接调用 requests.Session()、sqlite3.connect() 或实例化具体服务类(如 PaymentProcessorAlipay())。这类写法导致:测试时必须连真实网络或数据库;换支付渠道要改多处代码;类型提示无法约束接口行为,IDE 无补全、mypy 难校验。
用 ABC 定义协议,让调用方只认“能做什么”
抽象基类(ABC)不是为了强制继承,而是提供运行时可检查的契约。关键点在于:只声明方法签名,不实现逻辑;子类必须实现所有 @abstractmethod;调用方注解用 ABC 类型,而非具体类名。
- 定义
UserRepositoryABC,含get_by_id(self, user_id: int)和save(self, user: User) - 实现
SqliteUserRepo和PostgresUserRepo,都继承该 ABC - 业务类构造函数接收
repo: UserRepository参数,不 import 具体实现模块 - 运行时传入任一子类实例,类型检查器和 IDE 都能识别合法方法
构造函数注入 + ABC 是最稳妥的组合
仅用 ABC 不够——如果类还在内部自己创建实现类,就又回到硬编码。必须配合依赖注入,把控制权交出去。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
- 删掉类内
self.repo = SqliteUserRepo()这类语句 - 把
repo作为__init__的必填参数,类型注解为UserRepository - 启动入口(如
main.py)根据环境决定传哪个实现:UserService(SqliteUserRepo())或UserService(PostgresUserRepo()) - 测试时直接传
Mock(spec=UserRepository),无需 patch 或 reload 模块
容易忽略的细节:ABC 的 __subclasshook__ 和 Protocol 的取舍
Python 3.8+ 推荐优先用 typing.Protocol 替代 ABC,除非你需要运行时 isinstance() 检查。Protocol 更轻量,且支持结构化匹配(鸭子类型),而 ABC 要求显式 class X(ABC) 继承或注册。
立即学习“Python免费学习笔记(深入)”;
- 若只需类型提示和 IDE 支持,用
class UserRepository(Protocol)更干净 - 若业务逻辑里有
if isinstance(repo, UserRepository): ...,才用 ABC - 别在 ABC 里写具体方法实现——那会诱使子类误用
super().xxx(),破坏解耦初衷 -
__subclasshook__很少需要自定义,多数场景默认行为已足够

















