Python patch装饰器执行顺序不符预期,patch.dict行为异常求解
关于unittest.mock中patch.dict嵌套装饰器的行为疑问
测试代码
import os from unittest.mock import patch def sys_exit_new1(): print("sys_exit_new1:", os.environ.get("BANANA")) def sys_exit_new2(): print("sys_exit_new2:", os.environ.get("BANANA")) @patch("sys.exit", new_callable=sys_exit_new1) @patch.dict(os.environ, {"BANANA": "1"}) @patch.dict(os.environ, {"BANANA": "2"}) @patch("sys.exit", new_callable=sys_exit_new2) @patch.dict(os.environ, {"BANANA": "3"}) def test_mytest(m1, m2): pass test_mytest()
实际运行结果
sys_exit_new2: 2 sys_exit_new1: 2
官方文档说明
注意:装饰器从下往上应用,这是Python应用装饰器的标准方式。传入测试函数的mock对象顺序与此一致。
预期输出(原理解偏差)
sys_exit_new2: 3 sys_exit_new1: 1
原因解释
你遇到的问题核心是对patch装饰器的上下文执行时机和new_callable调用时机的误解,结合patch.dict的嵌套行为,具体拆解如下:
装饰器与上下文的执行逻辑
Python装饰器确实是从下往上应用(离函数最近的先包装),但调用被装饰函数时,上下文的进入顺序是从上到下(最外层装饰器先生效),退出则是从下到上恢复。而patch的new_callable是在进入当前patch上下文后,进入内层装饰器上下文前被调用的。patch.dict的嵌套覆盖规则
多个patch.dict作用于同一个字典时,会形成嵌套上下文:进入时内层设置会覆盖外层同键的值;退出时逐层恢复到上一层的状态。你的代码执行步骤
我们把装饰器从下到上编号,方便梳理:5. @patch("sys.exit", new_callable=sys_exit_new1) 4. @patch.dict(os.environ, {"BANANA": "1"}) 3. @patch.dict(os.environ, {"BANANA": "2"}) 2. @patch("sys.exit", new_callable=sys_exit_new2) 1. @patch.dict(os.environ, {"BANANA": "3"})调用
test_mytest()时的实际流程:- 先进入最外层的装饰器(编号5)上下文,准备创建
sys.exit的mock,此时会先进入内层的编号4和3的patch.dict上下文:- 编号4设置
BANANA=1,紧接着编号3覆盖为BANANA=2; - 此时调用
sys_exit_new1(),所以打印sys_exit_new1: 2;
- 编号4设置
- 继续进入编号2的
patch上下文,创建第二个sys.exit的mock,此时仍处于编号3的patch.dict上下文(BANANA=2),调用sys_exit_new2()后打印sys_exit_new2: 2; - 最后进入编号1的
patch.dict上下文,设置BANANA=3,执行测试函数体; - 执行完毕后,逐层退出所有上下文,恢复
os.environ的原始状态。
- 先进入最外层的装饰器(编号5)上下文,准备创建
预期偏差的根源
你误以为new_callable会在对应patch.dict的上下文内执行,但实际上:sys_exit_new2的调用时机是在进入编号1的patch.dict(3)之前,此时生效的是编号3的patch.dict(2)设置;sys_exit_new1的调用时机是在进入编号4和3的patch.dict之后,所以两者都打印BANANA=2。
内容的提问来源于stack exchange,提问作者Blazes
相关产品推荐
相关产品推荐

