monkey patching 有时不生效是因为目标函数被提前导入并绑定,patch 未更新已加载引用;需在所有依赖前执行、避免错误对象、注意方法绑定、返回合法 response 实例,且生产环境慎用,优先采用依赖注入等更安全方案。

Python 中 monkey patching 为什么有时不生效
因为目标函数在 patch 前已被导入并绑定到模块/类的命名空间里,后续 patch 只改了源模块,没动已加载的引用。比如 requests.get 被其他模块提前导入过,你再 patch requests 模块本身,调用方仍走旧函数。
- 务必在所有依赖该函数的代码 import 之前执行 patch(最安全是放在测试入口或
if __name__ == "__main__"开头) - 检查是否 patch 了错误的对象:要 patch
module.func,而不是from module import func后 patchfunc—— 后者只改本地变量 - 类方法 patch 要注意绑定:直接赋值
MyClass.method = new_func不会自动绑定self,得用types.MethodType(new_func, None, MyClass)或改写为静态方法再 wrap
patch requests.get 时返回 MockResponse 的常见写法
直接返回 dict 或 str 会报错,因为 requests.get() 期望返回一个 Response 实例,它有 .json()、.status_code 等属性和方法。
- 用
unittest.mock.Mock构造最小可用响应:mock_resp = Mock() mock_resp.json.return_value = {"ok": True} mock_resp.status_code = 200 mock_resp.text = '{"ok": true}' requests.get = lambda *a, **kw: mock_resp - 更稳妥用
requests.models.Response实例(需手动设置_content):from requests.models import Response r = Response() r._content = b'{"ok":true}' r.status_code = 200 r.headers['Content-Type'] = 'application/json' requests.get = lambda *a, **kw: r - 避免 patch 全局
requests.get,优先用pytest-mock的mocker.patch("requests.get", return_value=...),作用域可控
monkey patching 在生产环境的风险点
不是不能用,而是副作用太隐蔽:它会污染全局状态,影响并发行为、第三方库逻辑,甚至让类型检查器完全失效。
- 一旦 patch 了内置函数(如
open、time.sleep),所有同进程模块都会受影响,包括日志、数据库连接池等底层组件 - 异步框架(如
asyncio)中 patch 同步函数可能引发竞态,比如 patchtime.time但 event loop 内部也依赖它 - 某些包(如
numpy、cv2)在 import 时就做 C 层绑定,运行时 patch Python 层函数无效 - 热更新场景下,反复 patch 同一函数容易导致引用泄漏或装饰器叠加(比如多次
@patch(...))
替代 monkey patching 的更稳方案
真正想“绕过外部依赖”,优先考虑接口抽象 + 依赖注入,而不是动运行时行为。
- 把外部调用封装进可替换的 client 类,例如定义
class APIClient,测试时传入FakeAPIClient,生产传RealAPIClient - 用抽象基类(
ABC)约束依赖行为,mypy 和 IDE 都能校验,比 runtime patch 更早发现问题 - 对 HTTP 依赖,用
responses库拦截 requests 的真实网络请求,无需 patch 函数,兼容性更好且不影响其他模块 - 如果只是临时调试,用
breakpoint()+ 修改局部变量比 patch 全局函数更轻量、更易撤销
真正难的不是怎么 patch,而是判断该不该 patch —— 大多数时候,问题出在设计层,不在运行时补丁上。










