Python 修补顺序出乎意料

问题描述 投票:0回答:1

给出以下代码:

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):
    ...
 
test_mytest()

测试将产生:

sys_exit_new2: 2
sys_exit_new1: 2

有人可以解释一下为什么吗? 文档说:

请注意,装饰器是从底部向上应用的。这是Python应用装饰器的标准方式。传递到测试函数中的创建的模拟的顺序与此顺序匹配。

如果这是真的,我希望它输出:

sys_exit_new2: 3
sys_exit_new2: 1

所以

patch.dict
的行为有所不同。

python python-unittest patch
1个回答
0
投票

unittest.mock.patch
做了一些奇怪的事情,大多数装饰器,包括
unittest.mock.patch.dict
,都不会这样做。

第一次用

unittest.mock.patch
装饰函数时,它会生成一个修补程序包装器,并在包装器上设置一个
patchings
属性,其中包含要修补的内容列表。如果您再次应用
unittest.mock.patch
,它只会 更新
patchings
列表
,而不是生成新的包装器。

所以,当你装饰你的函数时,这些事情会按顺序发生:

  1. @patch.dict(os.environ, {"BANANA": "3"})
    生成一个包装器,将
    os.environ['BANANA']
    修补到
    "3"
    并调用原始函数。

  2. @patch("sys.exit", new_callable=sys_exit_new2)
    生成具有
    patchings
    属性的包装器。该包装器应用
    patchings
    列表中指定要应用的任何补丁,然后调用 BANANA-3 包装器。

  3. @patch.dict(os.environ, {"BANANA": "2"})
    生成一个包装器,将
    os.environ['BANANA']
    修补到
    "2"
    并调用
    patch
    包装器。

  4. @patch.dict(os.environ, {"BANANA": "1"})
    生成一个包装器,将
    os.environ['BANANA']
    修补到
    "1"
    并调用 BANANA-2 包装器。

  5. @patch("sys.exit", new_callable=sys_exit_new1)
    检测到 BANANA-1 包装器已具有
    patchings
    列表(继承自 BANANA-2 包装器,而 BANANA-2 包装器又继承自原始
    patch
    包装器)。它不会生成新的包装器,而是更新
    patchings
    列表并返回 BANANA-1 包装器。

所以最后,

test_mytest
被设置为BANANA-1包装器,它调用BANANA-2包装器,它调用
patch
包装器,它调用BANANA-3包装器,它调用原始函数。

当您调用

test_mytest
时,您正在调用 BANANA-1 包装器。这调用了 BANANA-2 包装器,它又调用了
patch
包装器。在调用
patch
包装器时,
os.environ['BANANA']
设置为
"2"
。此时,
patch
包装器应用
patchings
列表中显示要应用的两个补丁,因此它调用您的
sys_exit_new1
sys_exit_new2
函数。由于此时
os.environ['BANANA']
设置为
"2"
,这就是您的两个函数打印的内容。

© www.soinside.com 2019 - 2024. All rights reserved.