4

我阅读了文档和相当多的 stackoverflow 帖子,但没有找到明确的答案来解决我的疑问。

我想我了解命名空间包的用途。

我只对 Python>=3.3 和隐式命名空间包感兴趣 - 没有__init__.py.

问题

  1. 命名空间包是否应该只包含其他包,或者模块(即.py文件)也“允许”?

  2. 命名空间包是否应该仅用作“容器”包,或者它们也可以包含在常规包中

  3. 如果命名空间包仅作为容器有意义,我想我可以声明,每当我有一个真正的包文件夹时,它所有包含 python 模块的子文件夹也应该__init__.py?

# this is fine
ns_package/
+-- real_package/
   +-- __init.py__

# how about this?
real_package/
+-- __init.py__  # I have it for docs AND want to force the dir to be a real package
+-- ns_package/  # I would just like to avoid an empty __init__.py
   +-- amodule.py

我怀疑命名空间包仅作为容器才有意义,因为在另一种情况下,我将无法使用不同路径中的其他东西扩展命名空间,因为父级是一个必须在单个点中定义的真实包文件系统。因此,我不会获得命名空间包的主要优势。

语境

我之所以问,是因为在运行和导入模块(从项目的根目录)时,在常规包中带有隐式命名空间包的情况非常好。但是,它需要对安装脚本进行一些调整,我想知道我是否首先做了一些有缺陷的事情。

注意:我尝试使用隐式命名空间包主要不是因为我想利用它们的功能,而是因为我讨厌空__init__.py文件。我一开始以为python 3.3终于摆脱了这个,不再需要包__init__.py了,但似乎没有那么简单......

4

1 回答 1

5

首先:您使用命名空间包的动机是有缺陷的。__init__.py空文件没有问题;它们现在可能是空的,但稍后可以填充内容。即使它们保持空置也不会造成任何麻烦。

话虽如此,从技术上讲,将命名空间包放在常规包中并没有错。当您执行表单的导入时,import a.b.c每个组件都会单独解析,并且b可以是位于常规包中的命名空间包a。考虑以下目录布局:

.
└── a
    ├── b
    │   └── c.py
    └── __init__.py

然后你可以导入模块c

>>> import a.b.c
>>> a
<module 'a' from '/tmp/a/__init__.py'>
>>> a.b
<module 'a.b' (namespace)>
>>> a.b.c
<module 'a.b.c' from '/tmp/a/b/c.py'>

如您所见,所有组件都在命名空间a.b__file__属性设置为 时单独实例化None

然而,这种设置阻止了命名空间包的主要用途,即它们可以拆分到多个目录中。这是因为即使b是一个命名空间包,它也存在于a将被缓存的常规包中sys.modules,从而防止进一步搜索导入路径。例如,考虑以下目录布局:

.
├── dir1
│   └── parent
│       ├── child
│       │   ├── one.py
│       ├── __init__.py
├── dir2
│   └── parent
│       ├── child
│       │   └── two.py
│       └── __init__.py
└── main.py

有两个命名空间包dir1/parent/childdir2/parent/child. 但是,您只能使用其中一个,因为常规包dir1/parent会阻止访问另一个。让我们尝试以下内容main.py

import sys

sys.path.extend(('dir1', 'dir1'))

import parent.child.one  # this works

print(sys.modules['parent'])
print(sys.modules['parent.child'])
print(sys.modules['parent.child.one'])

import parent.child.two  # this fails

我们将得到以下输出:

<module 'parent' from 'dir1/parent/__init__.py'>
<module 'parent.child' (namespace)>
<module 'parent.child.one' from 'dir1/parent/child/one.py'>
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    import parent.child.two
ModuleNotFoundError: No module named 'parent.child.two'

这是因为sys.modules['parent']它是一个常规包,因此在import parent.child.two组件parent中被解析为那个包,它确实有一个属性child,但这个命名空间不包含two. 需要进一步搜索导入路径才能找到该模块。

具体回答您的问题:

1)您可以.py在命名空间包层次结构的任何级别拥有文件。只要它不包含__init__.py文件,它就被认为是一个命名空间包,它的内容会被相应地解析。考虑以下目录布局:

.
└── a
    ├── b
    │   ├── c
    │   │   └── three.py
    │   └── two.py
    └── one.py

您可以在任何命名空间包中导入任何模块:

>>> import a.one
>>> import a.b.two
>>> import a.b.c.three
>>> a.b.c
<module 'a.b.c' (namespace)>

2)如上所述,您可以将命名空间包放在常规包中,但这没有多大意义,因为它会阻止它们的预期用途。

3)这在很大程度上取决于您所说的“应该”是什么意思。从技术上讲,这__init__.py不是必需的,但它绝对很有意义。

如开头所述,__init__.py文件的目的不仅仅是指示常规的 Python 包,而且通常它们也充满了内容。如果没有,这没什么好担心的。

于 2020-07-20T09:50:57.567 回答