Python 解释器中的错误,还是我误解了包导入机制?

Bug in Python interpreter or am I misunderstanding package import mechanism?

我有以下源代码树:

planets/
    earth.py
    mars.py
util.py
main.py

使用以下代码:

planets/earth.py:

def moon() -> None:
    print('moon')

planets/mars.py:

def phobos() -> None:
    print('phobos')

util.py:

import planets.mars  # yes, just this

main.py:

import planets.earth
import util

def foobar() -> None:
    planets.mars.phobos()

if __name__ == '__main__':
    foobar()

请注意,在 "main.py" 中,我没有明确导入 "planets.mars"。当我 运行 "main.py" 时,我没有像预期的那样收到错误,而是得到了以下输出:

phobos

我好像是因为我在"util.py"中导入"planets.mars",然后在"main.py"中导入"util.py",所以"main.py"可以看到"planets.mars" 因此我可以从 "main.py".

调用 "planets.mars.phobos()"

但是,当我从 "main.py" 中删除 "import planets.earth" 时,我在尝试 运行 "main.py" 时遇到 "NameError: name 'planets' not defined" 异常。这似乎表明 "planets.mars" 通过导入 "util.py" 而被传递导入 "main.py" 并不是发生了什么,真正发生的是我的 [=61= 中存在错误] 口译员。

有人可以为我揭开这个谜团吗?我在 Debian 9 上使用 CPython 版本 3.7.0。

谢谢!

导入 planets.earthplanets 模块添加到 main.py 的命名空间,并将 planetsplanets.earth 插入 sys.modules

util.py 中导入 planets.mars 会将 planets.mars 插入 sys.modules 并使名称 mars 可用作 planets 的属性。

planets.mars.phobos在main.py中访问时,planet.mars可以通过main.py的命名空间中的名称planets访问。

如果从 main.py 中删除 planets.earth 的导入,planets 不再在模块命名空间中,因此会引发 NameError

如果删除 util.py 的导入,mars 不会设置为 planets 的属性,因此会引发 AttributeError

这种行为可能会造成混淆,这也是为什么许多包都有一个 __init__.py 导入子模块/子包以便在导入包时它们都可用的原因之一。