首先:您使用命名空间包的动机是有缺陷的。__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/child
和dir2/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 包,而且通常它们也充满了内容。如果没有,这没什么好担心的。