【问题标题】:Python - Unexpected Import OccuringPython - 发生意外的导入
【发布时间】:2026-01-11 05:05:01
【问题描述】:

我希望有人可以提供一些关于 Python3 在导入期间创建的额外名称绑定的见解。这是测试用例:

我创建了一个名为 spam 的测试包(我知道是原始的)。它包含以下3个文件:

文件内容如下:

__init__.py:

from .foo import Foo  
from .bar import Bar  

foo.py

def Foo():
    pass

bar.py

def Bar():  
    pass  

很简单的东西。当我导入spam 包时,我可以看到它创建了与spam 命名空间中的Foo()Bar() 函数 的名称绑定,这预期的。出乎意料的是,它还将名称绑定到spam 命名空间中的foobar 模块,如下所示。

更有趣的是,如果我在__main__ 中导入Foo()Bar() 函数,这些对模块的额外名称绑定不会发生,如下所示:

阅读有关导入语句的文档(语言参考和教程),我没有看到任何会导致这种情况发生的情况。

谁能解释一下为什么从包中的模块导入函数时,它还会将名称绑定到包含该函数的模块?

【问题讨论】:

    标签: python python-3.x ipython jupyter-notebook python-import


    【解决方案1】:

    是的 - 这是正确的,并且是 Python 导入机制的一部分。

    当您导入一个模块时,会发生很多事情,但我们可以关注一些:

    1) Python 检查模块是否已经加载——也就是说,它检查它是否是合格的 bane(带点的名称)在 sys.modules 下

    2) 如果没有,它实际上会加载模块:这包括检查预编译的缓存字节码文件、解析、编译 .py 文件等...

    3) 它实际上在import 命令中进行名称绑定:即“from .foo import Foo”在当前命名空间中创建一个变量“Foo”,指向“spam.foo.Foo” " 对象。

    认为模块总是作为一个整体加载 - 并在 sys.modules 字典中关联。除此之外,导入过程使该包中模块命名空间中可用的包的所有子模块在该包中可见 - 这就是导致名称“foo”和“bar”在您的 Spam 包中可见的原因。

    您可以在 __init__.py 文件的末尾删除名称“foo”和“bar”——但这会破坏 spam.foo 的预期导入和使用方式——基本上是:sys.modules["spam.foo"]将存在,但sys.modules["spam"].foo 不会 - 意味着之后 一个尝试做的事:

    import spam.foo
    spam.foo.Foo()
    

    Python 将在“foo”上产生名称错误。

    导入机制会将其报告为存在(它位于 sys.modules 中),因此它什么也不做。但是“spam.foo”已被删除,因此无法访问。

    【讨论】:

    • 感谢您的详细回复。我发现它布置得很好。我认为下面的引用说明了这一点:“除此之外,导入过程使该包中可见的模块名称空间中可用的包的所有子模块 - 这就是导致名称“foo”和“bar”的原因在您的垃圾邮件包中可见。” 您是否知道文档中有任何地方说明了这一点?还是你通过阅读源代码发现的?