【问题标题】:Python import mechanism and module mocksPython 导入机制和模块模拟
【发布时间】:2026-02-13 10:35:01
【问题描述】:

这更多是为了理解 Python(在本例中为 3.9)是如何工作的,而不是为了解决实际问题,所以请多多包涵,不要理会 m3 的荒谬方式。我只是想复制我正在处理的东西。

我有以下结构:

├── m1.py
└── m2
    └── m3
        ├── __init__.py
        └── m3.py

m2/m3/init.py:

from .m3 import *

m2/m3/m3.py:

def m3func():
    print('m3 func is here')

从现在开始,我将对 m1.py 进行更改

这是有效的,我期待它有效:

import m2.m3
m2.m3.m3func()

这并没有失败,因此它替换了 Mock 的模块。我也期待它能够以它的方式工作。

import sys
from unittest.mock import Mock
sys.modules['m2.m3'] = Mock()
import m2.m3 as alias
alias.m3func()

这个也一样

import sys
from unittest.mock import Mock
sys.modules['m2.m3'] = Mock()
from m2 import m3
m3.m3func()

我不明白这里发生了什么:

import sys
from unittest.mock import Mock
sys.modules['m2.m3'] = Mock()
import m2.m3
m2.m3.m3func()
m2.m3.m3func()
AttributeError: module 'm2' has no attribute 'm3'

import m2.m3from m2 import m3import m2.m3 as alias 有什么区别 我还有什么不明白的,有没有办法修复最后一个版本,这样它就不会抛出 AttributeError?我的示例 m2 是空的,但实际上,我不想完全换掉它,因为它确实包含我关心的东西。我只想针对 m3。关于使用这样的代码的最佳实践是否有建议:m2.m3.m3func()

【问题讨论】:

  • m2 中有 __init__.py 吗?
  • 不,但如果需要我可以添加一个。
  • 看看会发生什么
  • 我得到同样的错误
  • 你应该为你喜欢的答案投票,而不仅仅是选择它们

标签: python python-3.x unit-testing python-importlib


【解决方案1】:

如果你还没有,你绝对应该read the official documentation

每当您使用导入语句时,都会发生以下情况:

  1. Python 搜索模块,并处理沿途发现的新模块
  2. Python 引入了一个或多个变量以使用导入的任何内容

发现问题

Python 在内部使用sys.modules 来搜索模块,并在查找模块和父模块时更新其条目。因此,当您导入m2.m3.m3 时,它会添加带有键“m2”、“m2.m3”、“m2.m3.m3”的条目来存储引用这些模块的对象。您可以使用以下函数在每个语句之前/之后调试sys.modules

def print_status_relevant_modules():
    import sys
    print(sys.modules.get("m2", "<m2 not loaded>"))
    print(sys.modules.get("m2.m3", "<m2.m3 not loaded>"))
    print(sys.modules.get("m2.m3.m3", "<m2.m3.m3 not loaded>"))
    print()

每当 Python 发现一个新包(其中包含 __init__.py 文件的目录)或 module.py 模块时,它也会处理该模块。在这种情况下,当Python发现模块m2.m3的存在时,就会执行m2/m3/__init__.py的内容,从而执行导入语句from .m3 import *,导致发现模块m2.m3.m3(以及介绍局部变量m3funcm2.m3)。

您的问题开始于在 sys.modules 中使用键“m2.m3”模拟条目时,因为现在您已经中断了 Python 搜索模块的过程。因为你事先在 sys.modules 中模拟了模块 m2.m3 的条目,Python 认为它已经处理了这个模块,所以 Python 永远不会执行它的 __init__.py 文件。因此,m2.m3.m3 将永远不会被发现,不会添加任何条目,并且m3func 的局部变量将永远不会被引入m2.m3

如果您想知道为什么在模拟模块条目时没有看到任何错误,即使您调用了m3func(),那是因为模拟会接受任何调用,希望您稍后验证是否进行了某个调用。

不同的导入语句

所有不同的 import 语句之间的最大区别在于引入了哪些局部变量:

  • import m2.m3 产生一个带有属性m3 的局部变量m2;引入的变量是指表示模块m2 的 Module 实例
  • from m2 import m3 产生一个局部变量 m3;引入的变量引用了一个表示模块m2.m3的Module实例
  • import m2.m3 as alias 产生一个局部变量 alias;引入的变量引用了一个 Module 实例,表示模块 m2.m3

您可以在任何地方使用语句print(dir()) 来查看定义了哪些局部变量,或者在对象上。

print(dir())    # m2 is not defined
import m2.m3
print(dir())    # m2 is defined
print(dir(m2))  # shows that m2 has an attribute m3

作为一个额外的好处,m2/m3/__init__.py 中的语句 from .m3 import * 导致 m2.m3.m3 的所有局部变量都被导入到 m2.m3。在这种情况下,只添加了变量m3func

在使用import语句import m2.m3时,引入了局部变量m2,它引用了代表模块m2的Module实例。在搜索模块时,Python 应该已经发现了模块 m2.m3,并将属性 m3 添加到代表模块 m2 的 Module 实例中,以引用代表模块 m2.m3 的 Module 实例。但是,因为您事先模拟了sys.modules['m2.m3'],所以模块m2.m3 永远不会被发现,因此属性m3 永远不会添加到代表模块m2 的Module 实例中。当您尝试访问 m2.m3 时,这最终会导致错误。

使用import语句import m2.m3 as alias时,引入的局部变量alias指的是代表模块m3的Module实例。但是,因为你事先模拟了sys.modules['m2.m3'],Python 认为它已经发现了模块m2.m3,并返回sys.modules['m2.m3'] 的值。因此,变量 alias 最终引用了 Mock 实例而不是代表模块 m2.m3 的 Module 实例,并且您不会收到任何错误,因为 Mock 实例接受所有调用。

当你使用 import 语句from m2 import m3 时会发生同样的事情;变量m3 最终将引用Mock 实例。

如何解决您的问题

据我所知,您将 Python 的导入系统弄乱了,以至于您不能再依赖它来“仅使用”m2.m3m2.m3.m3。 Python 会想方设法以这样或那样的方式抱怨。

这可能是实际问题是设计问题的情况,mock永远不会是正确的答案,从长远来看只会导致更多问题,但是,我不知道实际情况是什么.但是,您应该尝试找到一种方法来避免这种情况。

【讨论】: