Python 3.3+中的包不需要__init__.py吗

我使用的是Python 3.5.1。我在这里阅读了文档和包部分:https://docs.python.org/3/tutorial/modules.html#packages

现在,我有以下结构:

/home/wujek/Playground/a/b/module.py

module.py:

class Foo:
def __init__(self):
print('initializing Foo')

现在,在/home/wujek/Playground中:

~/Playground $ python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x100a8f0b8>

类似地,现在在home, Playground的超级文件夹中:

~ $ PYTHONPATH=Playground python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x10a5fee10>

事实上,我可以做各种各样的事情:

~ $ PYTHONPATH=Playground python3
>>> import a
>>> import a.b
>>> import Playground.a.b

为什么会这样?我虽然需要__init__.py文件(空的会工作)在ab中,当Python路径指向Playground文件夹时,module.py是可导入的?

这似乎是从Python 2.7开始改变的:

~ $ PYTHONPATH=Playground python
>>> import a
ImportError: No module named a
>>> import a.b
ImportError: No module named a.b
>>> import a.b.module
ImportError: No module named a.b.module

~/Playground/a~/Playground/a/b中使用__init__.py可以正常工作。

168065 次浏览

Python 3.3+有隐式命名空间包,允许它在没有__init__.py文件的情况下创建包。

允许隐式命名空间包意味着要求提供一个__init__.py文件可以完全丢弃,并影响... .

__init__.py文件的旧方法在Python 2中仍然有效。

概述

@Mike的答案是正确的,但是太不精确。确实,Python 3.3+支持隐式命名空间包,这允许它创建一个没有__init__.py文件的包。这被称为名称空间包,与之相反的是常规的包装,它有一个__init__.py文件(空或不空)。

然而,创建名称空间包应该只在需要时才做。对于大多数用例和开发人员来说,这并不适用,所以你应该坚持使用__init__.py文件。

命名空间包用例

为了演示这两种类型的python包之间的区别,让我们看下面的例子:

google_pubsub/              <- Package 1
google/                 <- Namespace package (there is no __init__.py)
cloud/              <- Namespace package (there is no __init__.py)
pubsub/         <- Regular package (with __init__.py)
__init__.py <- Required to make the package a regular package
foo.py


google_storage/             <- Package 2
google/                 <- Namespace package (there is no __init__.py)
cloud/              <- Namespace package (there is no __init__.py)
storage/        <- Regular package (with __init__.py)
__init__.py <- Required to make the package a regular package
bar.py

google_pubsubgoogle_storage是单独的包,但它们共享相同的命名空间google/cloud。为了共享相同的命名空间,需要使公共路径的每个目录都成为一个命名空间包,即google/cloud/这应该是创建名称空间包的唯一用例,否则就不需要它了。

googlegoogle/cloud目录中没有__init__py文件,这一点很重要,这样两个目录都可以解释为名称空间包在Python 3.3+中,sys.path上任何名称与正在查找的包名匹配的目录都将被识别为该包的贡献模块和子包。因此,当你同时从google_pubsubgoogle_storage导入时,Python解释器将能够找到它们。

这与普通的包不同,后者是自包含的,这意味着所有部分都位于相同的目录层次结构中。当导入一个包时,Python解释器在sys.path上遇到一个带有__init__.py文件的子目录时,它将创建一个单目录包,其中只包含来自该目录的模块,而不是在该目录之外找到所有适当命名的子目录。对于不希望共享名称空间的包来说,这是完全没问题的。我强烈建议看一看Python导入系统中的粗心陷阱,以更好地理解Python导入常规包和命名空间包的行为,以及需要注意的__init__.py陷阱。

总结

  • 如果你想创建名称空间包,只需要跳过__init__.py文件。只有当您有位于不同位置的不同库,并且希望它们各自为父包贡献一个子包(即命名空间包)时,才创建命名空间包。
  • 继续在你的目录中添加空的__init__.py,因为99%的时候你只想创建普通的包。此外,诸如mypypytest之类的Python工具需要空的__init__.py文件来相应地解释代码结构。如果不小心,这可能会导致奇怪的错误。

资源

我的回答只触及了普通的包名称空间包工作的表面,所以请查看以下资源以获得进一步的信息:

我想说的是,只有当一个人想要有隐式名称空间包时,他才应该省略__init__.py。如果你不知道它的意思,你可能不想要它,因此即使在Python 3中你也应该继续使用__init__.py

如果你的项目中有setup.py,并且在其中使用了find_packages(),那么必须在每个目录中都有一个__init__.py文件,以便自动找到包。

包只有包含__init__.py文件时才会被识别

乌利希期刊指南:如果你想使用隐式命名空间包而不使用__init__.py,你只需要使用find_namespace_packages()

Docs

根据我的经验,即使使用python 3.3+,有时仍然需要空__init__.py。一种情况是您希望将子文件夹引用为包。例如,当我运行python -m test.foo时,直到我在test文件夹下创建了一个空的__init__.py,它才正常工作。我说的是最近的3.6.6版本。

除此之外,即使是出于与现有源代码或项目指南兼容的原因,在你的包文件夹中有一个空的__init__.py也是很好的。