为什么每个层级都需要__init__.py文件?

31

假设我有以下目录结构,其中.表示当前工作目录

.
\---foo
    \---bar
        \---__init__.py
        \---baz.py

当我运行python -c "import foo.bar.baz"时,我得到了

Traceback (most recent call last):
  File "<string>", line 1
ImportError: No module named foo.bar.baz
如果我使用echo "" > foo/__init__.py,上述命令会正常工作。我是否做错了什么或者我误解了__init__.py的意义?我认为它是用来阻止模块在不该存在的地方存在,例如一个名为string的目录,但如果你在我的例子中将foo替换为string,似乎我被迫创建永远不应该使用的模块,只为了能够引用层次结构更深的文件。
更新:
我正在使用一个生成__init__.py并强制执行目录结构的构建系统,虽然我可以调整层次结构,但我更愿意自己添加__init__.py。稍微改变一下问题,为什么我需要在每个级别都有一个Python包而不仅仅是在顶部?这只是一个规则,你只能从Python路径或Python路径下的一系列包链导入模块吗?

4
确实如此,你可以使用 import bar.baz 进行导入,这是有效的。但是,在 foo 没有 __init__.py 文件之前,它不会被识别为一个包。 - cs95
2
是的,__init__.py 表示“你可以导入我”。 - voy
这个问题与众不同,因为我不是在问 __init__.py 如何创建一个模块,而是更多地询问什么使得一个模块可以被导入。你只能通过 Python 路径上的目录链来导入一个模块吗?还是你可以通过包和目录链来导入一个模块呢? - quittle
1个回答

20

如果您想将目录视为模块,则需要此文件。

要使Python将目录视为包含包,需要使用__init__.py文件;这是为了防止具有相同名称(例如string)的目录无意中隐藏出现在模块搜索路径后面的有效模块。在最简单的情况下,__init__.py可以只是一个空文件,但它也可以执行包的初始化代码或设置稍后描述的__all__变量。

https://docs.python.org/3/tutorial/modules.html#packages

在一个__init__.py文件中,您有很大的可能性记录模块,通过在第一层级提供最有用的对象(类/函数)来摆脱用户/开发者的嵌套导入... ...实际上尽可能简单易用。 问题更新后进行编辑 默认的导入器/查找器(检查sys.meta_path)如下:
  1. BuiltinImporter - 搜索/加载内置模块
  2. FrozenImporter - 搜索/加载冻结模块(例如*.pyc)
  3. PathFinder - 您感兴趣的那个,允许基于文件系统搜索/加载模块
第三个是__init__.py的事情(实际上FrozenImporter也是)。
路径查找器PathFinder会在sys.path中的路径(以及包中定义的__path__)中搜索模块。该模块可以是独立的Python文件(如果它在搜索路径的根目录中),也可以是带有__init__.py的目录。
参考您的示例:
foo/
  bar/
    __init__.py
    baz.py
  • 如果你在foo/中创建__init__.py,那么foo.bar.baz将可用(就像你所说的)。

  • 如果你将foo/添加到sys.path或通过PYTHONPATH=foo/传递它,那么bar.baz将可用(注意没有父模块foo)。

  • 如果你自己编写finder(和loader),你可以加载任何你想要的文件,无论它在哪里。这给了你很大的能力。例如,看一下stack-overflow-import,它基于SO的搜索结果暴露代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接