python patch
说到Python里的patch很多人第一反应就是单元测试里的mock.patch。但如果你只用过这一个场景可能会错过它更本质的东西。让我从头说起。在平时写代码时总会遇到一些不太方便直接调用的场景。比如一个函数依赖网络请求或者某个模块加载了配置。这时候如果能把原来的代码临时“替换”成我们想要的版本事情就简单多了。patch做的就是这件事——它允许你在运行时动态替换对象的属性完成后还能自动恢复。打个比方就像你在厨房做饭发现冰箱里的鸡蛋不够了。正常的做法是下楼去买但如果你现在正忙着炒菜可能更希望有个魔法手指一点冰箱里就凭空出现几个鸡蛋。patch就是那个魔法手指它让程序在运行时“假装”某些东西存在或不存在。patch能干什么最常见的场景是测试。假设你有一个函数叫get_weather它依赖一个外部API。如果直接调用每次测试都要联网慢不说万一接口挂了测试就过不了。这时候就可以patch掉底层网络请求让它直接返回一个固定数据。但patch的应用远不止测试。有些早期代码里写死了全局配置你管不了全部调用处就可以在测试启动时用patch把配置“替换”成测试专用版本。还有那些用单例模式写的类测试时需要全新实例也可以借助patch暂时清空单例状态。更高级一点patch可以用来做监控。比如你想统计某函数被调用了多少次或者记录它的传参但不想改动原函数代码就可以用patch包装它。怎么用patchPython里patch通常指的是unittest.mock.patch但更底层其实有更通用的方法直接操作对象的__dict__或者getattr/setattr。不过日常工作中mock.patch已经足够。基本用法是作为上下文管理器fromunittest.mockimportpatchwithpatch(module.function)asmock_func:mock_func.return_value42resultsome_function_that_calls_module_function()也可以做装饰器patch(module.function)deftest_something(mock_func):mock_func.return_value42assertsome_function()42一个很容易踩的坑patch的目标字符串必须是你代码里实际引用变量的地方。比如你的代码写的是from requests import get然后代码里直接用了get()那patch的时候就要写代码文件的真正引用路径而不是requests.get。简单说要patch的是“这个对象在哪个命名空间里被引用”而不是“它原本定义在哪里”。如果patch的是类上的方法要注意bound method和unbound method的区别。比如classMyClassdefmethod(self):returnrealwithpatch.object(MyClass,method,return_valuefake):objMyClass()print(obj.method())# 会输出什么这个例子里patch替换的是类属性所以obj.method调用的其实是patch后设置的新属性会输出’fake’。但如果直接替换实例属性那这个替换只对当前实例生效。最佳实践很多人喜欢大量用patch觉得测试写起来快。但其实patch用多了会让测试变得脆弱——它实际上跳过了真正的执行路径只是验证了假数据的传递。所以有几个原则可以考虑只在无法避免的地方用patch。比如网络请求、文件操作、时间相关。如果你自己写的业务逻辑里有很多依赖先用依赖注入重构而不是靠patch硬来。明确patch的范围。上下文管理器比装饰器更灵活可以精确控制patch生效的代码块。一个测试函数里如果多个patch都生效很容易搞不清到底替换了哪里。小心异步。async函数里的patch和普通函数稍有不同需要用patch.object的方式或者配合AsyncMock。如果直接patch一个async函数返回的mock对象不会自动变成可await的。对于第三方库的patch尽量精确到具体函数而不是整个模块。比如不要patch(requests)而是patch(requests.get)。这样影响范围小也更容易排查问题。记得恢复。unittest.mock的patch会自动恢复但也有人用手动修改__dict__的方式这时候务必用try/finally包裹否则测试失败会影响后续代码。与同类技术对比Python里能实现类似动态替换的还有几种方式。第一种是修改类或实例的属性。直接赋值当然最快但不会自动恢复。如果需要手动写reset代码测试里就会多出一堆无关逻辑很烦。第二种是monkey-patching。其实就是动态赋值不加管理。在少量场景下可以用比如某个库有bug临时绕过但不推荐作为常规手段。第三种是pytest的monkeypatch fixture。这个比unittest.mock.patch更灵活因为它能直接替换任意对象属性甚至能删除环境变量。但缺点是不提供assert机制比如你想验证函数被调用了几次就不方便。选择monkeypatch还是mock.patch取决于你更关注“替换”还是“验证”。还有种更强大的工具叫asyncmock专门处理异步代码的patch。它能自动处理异步上下文和迭代器写异步测试时比mock.patch省心不少。最后聊聊依赖注入。很多人觉得patch是“脏”做法依赖注入才是正道。其实两者侧重点不同依赖注入是从设计层面保证代码可测试patch是在已有代码没设计好的情况下做测试补丁。如果项目从零开始先考虑依赖注入。但面对几万行老代码用patch快速加测试可能更现实。说到结尾patch本质上是对Python动态特性的利用。它既能做测试工具也能做AOP面向切面编程的一种轻量实现。理解它的原理——其实就是控制了对象查找路径上的某个节点——比记住一堆API更重要。遇到复杂问题时去理解Python的对象模型和属性查找机制会发现自己还能做出很多超出mock.patch本身的设计。