为什么在子包的 __init__.py 中导入特定子包模块的方法会用子包填充包命名空间?

Why does importing a specific subpackage module's method in the subpackage's __init__.py populate the package namespace with the subpackage?

我用的是Python3.9.10

我知道提出的问题相当冗长和令人困惑,但是我想不出更好的措辞方式,所以请允许我在下面说明问题:

我有一个包,test_import,结构如下:

test_import/
├── __init__.py
├── moduleA.py
└── moduleB/
   ├── __init__.py
   └── moduleB.py

test_import/__init__.py 包含以下内容:

from . import moduleA
from . import moduleB

test_import/moduleA.py 包含以下内容:

def test_func_module_a(s: str):
    print(f"test_import.moduleA.test_func_module_a        : {s}")

test_import/moduleB/moduleB.py 包含以下内容:

def test_func_module_b(s: str):
    print(f"test_import.moduleB.moduleB.test_func_module_b: {s}")

最后,test_import/moduleB/__init__.py 包含以下内容:

from .moduleB import test_func_module_b

最后,我的 $PYTHONPATH 中有 test_import,我有一个 python 文件 test_main.py,它导入并使用(不太有用的) test_import图书馆:

import test_import as ti

ti.moduleA.test_func_module_a("ti.moduleA.test_func_module_a")
ti.moduleB.test_func_module_b("ti.moduleB.test_func_module_b")
ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")

当我 运行 test_main.py 时,我希望看到以下内容:

my.username@myMac:Desktop$ python3 test_main.py 
test_import.moduleA.test_func_module_a        : ti.moduleA.test_func_module_a
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.test_func_module_b
Traceback (most recent call last):
  File "/Users/my.username/Desktop/test_import_main.py", line 11, in <module>
    ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'moduleB'

我希望看到这个,因为在 test_import/__init__.py 中,我只将特定方法 test_func_module_b 导入到属于 test_import.moduleB 的名称空间中。我没有在 test_import/moduleB/__init__.py 中导入 moduleB.py 模块,即 from . import moduleB.

但是,当我 运行 test_main.py 时,我得到以下结果,没有引发任何与名称空间相关的 AttributeError:

my.username@myMac:Desktop$ python3 test_main.py 
test_import.moduleA.test_func_module_a        : ti.moduleA.test_func_module_a
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.test_func_module_b
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.moduleB.test_func_module_b

即没有与 test_import.moduleB 命名空间相关的 AttributeError。

这令人困惑,因为(正如我上面所说),我没有将 moduleB.py 导入 test_import.moduleB 的命名空间。

如果我将 test_import/__init__.py 保留为一个空文件,并且 运行 test_main.py,以下两行都会引发 AttributeErrors,与缺少在子包的 (test_import.moduleB) 命名空间中,即:

my.username@myMac:Desktop$ python3 test_import_main.py 
test_import.moduleA.test_func_module_a        : ti.moduleA.test_func_module_a
Traceback (most recent call last):
  File "/Users/my.username/Desktop/test_import_main.py", line 10, in <module>
    ti.moduleB.test_func_module_b("ti.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'test_func_module_b'

my.username@myMac:Desktop$ python3 test_main.py 
test_import.moduleA.test_func_module_a        : ti.moduleA.test_func_module_a
Traceback (most recent call last):
  File "/Users/my.username/Desktop/test_import_main.py", line 11, in <module>
    ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'moduleB'

谁能解释一下 Python 的导入系统是如何做到这一点的?

PS - 由于 Whosebug 上有很多与导入相关的问题,我希望会有类似的问题。但是,我一直找不到这个特定问题的答案。

当 Python 作为导入的一部分遇到 子模块时 from x.y import z 中的 y(绝对)或 from .y import z(相对)),它将在父模块的命名空间中放置对子模块的引用。 the docs:

中对此进行了概述

When a submodule is loaded using any mechanism [...] a binding is placed in the parent module’s namespace to the submodule object.
[...]
The invariant holding is that if you have sys.modules['spam'] and sys.modules['spam.foo'] [...] the latter must appear as the foo attribute of the former.