从一个包中访问一个模块,该包的初始文件通过 AS 关键字导入该模块

accessing a module from a package whose init file imports that module via AS keyword

抱歉,标题令人困惑。至少我试过了!这是我的目录结构:

root\
    mypackage\
        __init__.py
        mymodule.py
    main.py

mymodule.py

print('inside mymodule.py')

__init__.py

print('inside __init__')
from . import mymodule as m

main.py

import mypackage
print(mypackage.m)
print(mypackage.mymodule)  # <--- Why does it work?

和输出:

inside __init__
inside mymodule.py
<module 'mypackage.mymodule' from 'C:\Users...\mypackage\mymodule.py'>
<module 'mypackage.mymodule' from 'C:\Users...\mypackage\mymodule.py'>

main.py文件中,当我导入mypackage时,这个标签实际上是指__init__.py文件,所以我可以访问该模块中的所有objects/labels。 mypackage.m 起作用是有道理的,因为 m 现在是 __init__.py 全局命名空间中的一个符号。

但是 __init__.py 的命名空间中没有 mymodule key/symbol 因为我通过 [=25= 将 mymodule 符号重新绑定到 m 标签].

问题: 那么为什么这个 print(mypackage.mymodule) 可以正常工作而不抛出任何异常?

附加信息:如果我在包中有另一个模块,比方说 temp.py,那么 print(mypackage.temp) 将不起作用,因为 mypackage 再次引用 __init__.py
另外对我来说有趣的是,如果我在 __init__.py 中写 print(mymodule) 并且我 运行 在 main.py 模块中,它会 运行 正确。

简答

经过两天使用导入语句并搜索文档后,我找到了答案。首先,我要说明发生这种情况的原因,然后再进行更详细的解释。 (另请参阅底部的更新部分

从文档中看这个link,也查看那个link:

中例子中提到的结构树

If __all__ is not defined, the statement from sound.effects import * does not import all submodules from the package sound.effects into the current namespace; it only ensures that the package sound.effects has been imported (possibly running any initialization code in __init__.py) and then imports whatever names are defined in the package. This includes any names defined (and submodules explicitly loaded) by __init__.py.

我们如何在main.py中导入mypackage包并不重要,import mypackagefrom mypackage import *形式都没有关系。

通过这样做,Python 导入了 __init__.py 模块中定义的所有名称,如我们在上面看到的 m显式加载子模块(此处为 mymodule 模块)。正确地说,它将 'mymodule' 键添加到 __init__.py 的全局命名空间。

让我们更详细地了解一下:

我要稍微改变一下 __init__.py 这样我们就可以 运行 它直接作为 main 模块(因为我们使用了相对导入在里面我们不能那样做)然后我将打印它的全局命名空间中的内容。 (不要忘记用 PYTHONPATH 添加 mypackage 目录)

# __init__.py
print('inside __init__')
from mypackage import mymodule as m
print("--------------------------------")
for k, v in globals().copy().items():
    if not k.startswith('__'):
        print(k)

输出:

inside __init__
inside __init__
inside mymodule.py
--------------------------------
mymodule
m
--------------------------------
m

你看到 "inside __init__" 打印语句两次,因为这个文件被执行了两次,一次是自己执行,然后是通过执行这一行:from mypackage import mymodule as m

很明显,在虚线下,我们得到了不同的输出。第一个 mymodulem,第二个只有 m.

当我们直接运行 __init__.py时,一条记录被添加到名为'__main__'sys.modules中。但是当我们导入 mypackage 时,另一条记录被添加到名为 mypackagesys.modules 中。有趣的是它们都指向同一位置的同一个文件但是从这些文件创建的模块对象是不一样的。

为了演示这一点,我将在 mymodule.py 中添加几行代码。这有助于我们查看这些文件和模块:

# mymodule.py
print('inside mymodule.py')

import sys
v1 = sys.modules['mypackage']
v2 = sys.modules['__main__']
print(v1)
print(v2)
print(f'v1 is v2: {v1 is v2}')
print(f'v1 == v2: {v1 == v2}')
print(f'v1.__dict__ == v2.__dict__: {v1.__dict__ == v2.__dict__}')
print('mypackage', list(v1.__dict__))
print('__main__', list(v2.__dict__))

现在让我们再次直接运行__init__.py模块!

inside __init__
inside __init__
inside mymodule.py
<module 'mypackage' from 'C:\Users...\mypackage\__init__.py'> # these are the same
<module '__main__' from 'C:\Users...\mypackage\__init__.py'> # these are the same 
v1 is v2: False
v1 == v2: False
v1.__dict__ == v2.__dict__: False
mypackage ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__']
__main__ ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__']
--------------------------------
mymodule
m
--------------------------------
m

如输出所示,它们完全不同。 v1 是包,因为它有 __path__.

Python 将 'mymodule' 键添加到 __init__.py 的命名空间的唯一情况是当我们导入 import mypackage 并加载所有显式加载的子模块时__init__.py.


更新:

我从文档中找到了一个页面,它恰好解决了这个问题:
https://docs.python.org/3/reference/import.html#submodules

When a submodule is loaded using any mechanism, a binding is placed in the parent module’s namespace to the submodule object.