从<module>导入...在__init__.py中会使模块名称可见吗?

15 投票
2 回答
2985 浏览
提问于 2025-04-16 17:54

来看下面这个代码示例:

文件 package1/__init__.py

from moduleB import foo
print moduleB.__name__

文件 package1/moduleB.py

def foo(): pass

然后从当前目录开始:

>>> import package1
package1.moduleB

这段代码在CPython中可以正常工作。让我感到惊讶的是,在 __init__.py 文件中的 from ... import 语句让 moduleB 这个名字变得可见。根据Python文档,这本不应该是这样的:

这种形式不会绑定模块名

有人能解释一下为什么CPython会这样工作吗?有没有什么文档详细描述这个问题?

2 个回答

0

这是因为 __init__.py 在运行时被当作 package1 模块对象,所以每个 .py 文件都会被视为一个子模块。而重写 __all__ 并没有什么意义。你可以创建另一个文件,比如 example.py,并把 __init__.py 中的相同代码放进去,这样会引发 NameError 错误。

我觉得 CPython 运行时在处理 __init__.py 时寻找变量的方式和其他 Python 文件不同,可能是这样的:

looking for variable named "moduleB"
if not found:
   if __file__ == '__init__.py': #dont raise NameError, looking for file named moduleB.py
         if current dir contains file named "moduleB.py":
                      import moduleB
         else:
                    raise namerror
6

文档可能让你产生误解,因为它主要描述的是从父包外部导入模块的常见情况。

举个例子,当我在自己的代码中使用“from example import submodule”,其中“example”是一个与我的代码完全无关的第三方库,这并不会把“example”这个名字绑定到我的代码里。不过,它确实会导入example/__init__.py和example/submodule.py这两个模块,创建两个模块对象,并把example.submodule分配给第二个模块对象。

但是,从子模块导入名字时,必须在父包对象上设置子模块的属性。想象一下如果不这样做会怎样:

  1. 当导入包时,package/__init__.py会被执行。

  2. 这个__init__文件执行“from submodule import name”。

  3. 过了一段时间,其他完全不同的代码执行“import package.submodule”。

在第3步时,要么sys.modules["package.submodule"]不存在,这样再次加载它会导致你得到两个在不同作用域中的不同模块对象;要么sys.modules["package.submodule"]存在,但“submodule”不会是父包对象(sys.modules["package"])的一个属性,这样“import package.submodule”就不会有任何效果。然而,如果什么都不做,使用这个导入的代码就无法通过包来访问submodule了!


理论上,如果导入机制的其他部分也做了相应的改变,导入子模块的方式是可以改变的。

如果你只想知道从包P导入子模块S会发生什么,简单来说:

  1. 确保P已经被导入,或者以其他方式导入它。(这一步会递归处理“import A.B.C.D”。)
  2. 执行S.py以获取一个模块对象。(省略.pyc文件等细节。)
  3. 将模块对象存储在sys.modules["P.S"]中。
  4. setattr(sys.modules["P"], "S", sys.modules["P.S"])
  5. 如果这个导入是“import P.S”的形式,就在本地作用域中绑定“P”。

撰写回答