Python 导入机制和模块模拟
Python import mechanism and module mocks
这更多的是为了理解 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.m3
、from m2 import m3
和import m2.m3 as alias
有什么区别
还有什么我不明白的,有没有办法修复最后一个版本,这样它就不会抛出 AttributeError?我的示例 m2 是空的,但实际上,我不想完全换掉它,因为它确实包含我关心的东西。我只想瞄准 m3。就最佳实践而言,是否有建议使用这样的代码:m2.m3.m3func()
?
你绝对应该 read the official documentation,如果你还没有的话。
只要您使用 import 语句,就会发生以下情况:
- Python搜索模块,并处理沿途发现的新模块
- Python 引入一个或多个变量以使用导入的任何内容
发现问题
Python 在内部使用 sys.modules
来搜索模块,并在沿途找到模块和父模块时更新其条目。因此,当您导入 m2.m3.m3
时,它会添加带有关键字 'm2'、'm2.m3'、'm2.m3.m3' 的条目来存储引用这些模块的对象。您可以使用以下函数调试 sys.modules
before/after 每个语句。
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
的内容,从而执行import语句from .m3 import *
,从而发现模块的m2.m3.m3
(以及引入局部变量m3func
到m2.m3
)。
当开始模拟 sys.modules 中带有键“m2.m3”的条目时,您的问题就开始了,因为现在您已经中断了 Python 的模块搜索过程。因为你事先在sys.modules中模拟了模块m2.m3
的条目,Python认为它已经处理了这个模块,所以Python永远不会执行它的__init__.py
文件。结果,m2.m3.m3
永远不会被发现,不会添加任何条目,m3func
的局部变量也永远不会被引入m2.m3
。
如果您想知道为什么在模拟模块的条目时没有看到任何错误,即使您正在调用 m3func()
,那是因为模拟将接受任何调用,期望您稍后验证某个调用
不同的导入语句
所有不同导入语句之间的最大区别在于引入了哪些局部变量:
import m2.m3
导致具有属性 m3
的局部变量 m2
;引入的变量是指表示模块 m2
的 Module 实例
from m2 import m3
导致局部变量 m3
;引入的变量是指表示模块 m2.m3
的 Module 实例
import m2.m3 as alias
导致局部变量 alias
;引入的变量是指表示模块 m2.m3
的 Module 实例
您可以在任何地方或在对象上使用语句 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 m2.m3
时,引入了局部变量m2
,它引用了一个代表模块m2的Module
实例。在搜索模块时,Python 应该发现了模块 m2.m3,并在表示模块 m2 的 Module
实例中添加了属性 m3
,以引用 Module
代表模块 m2.m3 的实例。但是,因为您事先模拟了 sys.modules['m2.m3']
,模块 m2.m3 永远不会被发现,因此属性 m3
永远不会添加到代表模块 m2 的 Module
实例中。当您尝试访问 m2.m3
.
时,这最终会导致错误
当你使用导入语句import m2.m3 as alias
时,引入的局部变量alias
引用了代表模块m3的Module
实例。但是,因为你事先mock了sys.modules['m2.m3']
,Python认为它已经发现了模块m2.m3,returns sys.modules['m2.m3']
的值。因此,变量 alias
最终引用 Mock
实例而不是表示模块 m2.m3 的 Module
实例,并且您不会收到任何错误,因为 Mock
实例接受所有电话。
当你使用 import 语句 from m2 import m3
时会发生同样的事情;变量 m3
最终将引用 Mock
实例。
如何解决您的问题
据我所知,你把 Python 的导入系统搞砸了,以至于你不能再依赖它来“只使用”m2.m3
或 m2.m3.m3
。 Python 会想办法以这种或那种方式抱怨。
这可能是实际问题是设计问题的情况,mocking 永远不会是正确的答案,而且只会在很长一段时间内造成更多问题 运行,但是,我不知道实际情况是怎样的。但是,您应该尝试找到一种避免这种情况的方法。
这更多的是为了理解 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.m3
、from m2 import m3
和import m2.m3 as alias
有什么区别
还有什么我不明白的,有没有办法修复最后一个版本,这样它就不会抛出 AttributeError?我的示例 m2 是空的,但实际上,我不想完全换掉它,因为它确实包含我关心的东西。我只想瞄准 m3。就最佳实践而言,是否有建议使用这样的代码:m2.m3.m3func()
?
你绝对应该 read the official documentation,如果你还没有的话。
只要您使用 import 语句,就会发生以下情况:
- Python搜索模块,并处理沿途发现的新模块
- Python 引入一个或多个变量以使用导入的任何内容
发现问题
Python 在内部使用 sys.modules
来搜索模块,并在沿途找到模块和父模块时更新其条目。因此,当您导入 m2.m3.m3
时,它会添加带有关键字 'm2'、'm2.m3'、'm2.m3.m3' 的条目来存储引用这些模块的对象。您可以使用以下函数调试 sys.modules
before/after 每个语句。
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
的内容,从而执行import语句from .m3 import *
,从而发现模块的m2.m3.m3
(以及引入局部变量m3func
到m2.m3
)。
当开始模拟 sys.modules 中带有键“m2.m3”的条目时,您的问题就开始了,因为现在您已经中断了 Python 的模块搜索过程。因为你事先在sys.modules中模拟了模块m2.m3
的条目,Python认为它已经处理了这个模块,所以Python永远不会执行它的__init__.py
文件。结果,m2.m3.m3
永远不会被发现,不会添加任何条目,m3func
的局部变量也永远不会被引入m2.m3
。
如果您想知道为什么在模拟模块的条目时没有看到任何错误,即使您正在调用 m3func()
,那是因为模拟将接受任何调用,期望您稍后验证某个调用
不同的导入语句
所有不同导入语句之间的最大区别在于引入了哪些局部变量:
import m2.m3
导致具有属性m3
的局部变量m2
;引入的变量是指表示模块m2
的 Module 实例
from m2 import m3
导致局部变量m3
;引入的变量是指表示模块m2.m3
的 Module 实例
import m2.m3 as alias
导致局部变量alias
;引入的变量是指表示模块m2.m3
的 Module 实例
您可以在任何地方或在对象上使用语句 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 m2.m3
时,引入了局部变量m2
,它引用了一个代表模块m2的Module
实例。在搜索模块时,Python 应该发现了模块 m2.m3,并在表示模块 m2 的 Module
实例中添加了属性 m3
,以引用 Module
代表模块 m2.m3 的实例。但是,因为您事先模拟了 sys.modules['m2.m3']
,模块 m2.m3 永远不会被发现,因此属性 m3
永远不会添加到代表模块 m2 的 Module
实例中。当您尝试访问 m2.m3
.
当你使用导入语句import m2.m3 as alias
时,引入的局部变量alias
引用了代表模块m3的Module
实例。但是,因为你事先mock了sys.modules['m2.m3']
,Python认为它已经发现了模块m2.m3,returns sys.modules['m2.m3']
的值。因此,变量 alias
最终引用 Mock
实例而不是表示模块 m2.m3 的 Module
实例,并且您不会收到任何错误,因为 Mock
实例接受所有电话。
当你使用 import 语句 from m2 import m3
时会发生同样的事情;变量 m3
最终将引用 Mock
实例。
如何解决您的问题
据我所知,你把 Python 的导入系统搞砸了,以至于你不能再依赖它来“只使用”m2.m3
或 m2.m3.m3
。 Python 会想办法以这种或那种方式抱怨。
这可能是实际问题是设计问题的情况,mocking 永远不会是正确的答案,而且只会在很长一段时间内造成更多问题 运行,但是,我不知道实际情况是怎样的。但是,您应该尝试找到一种避免这种情况的方法。