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

标签 python import package python-import python-packaging

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

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

我的模块.py

print('inside mymodule.py')

__init__.py

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

主.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文件,所以我可以访问该模块内的所有对象/标签。 mypackage.m 起作用是有道理的,因为 m 现在是 __init__.py 全局命名空间中的一个符号。

但是 __init__.py 的命名空间中没有 mymodule 键/符号,因为我将 mymodule 符号重新绑定(bind)到 m 标签通过 as m

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

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

最佳答案

简答

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

看看这个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 mypackage,要么是from 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时,会在sys.modules中添加一条名为'__main__'的记录。但是当我们导入 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.

关于python - 从一个包中访问一个模块,该包的初始文件通过 AS 关键字导入该模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70152714/

相关文章:

python - 无法在 Windows 上安装 textract

python - 分发一个简单的 python 脚本

python - 使用python连接远程服务器,添加代理

python - 从列表中删除空的 Counter() 对象

python - 如何使用python日期时间函数/增量?

python - 要求用户在首次登录时更改密码?

javascript - 在 localStorage 中导出数据以供以后重新导入

mongodb - 尽管 mongodb 中有数据,为什么 db.database.find() 没有结果?

java - 为什么JDBC是动态加载而不是导入的?

java - 查找项目中的所有包