企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 8.5 【进阶】常规包与空间命名包 ## 1. 常规包 在 Python 3.3 之前或者说 Python 2 中,一个包想要被导入使用,那么该包内必须要有 `__init__.py` 文件,这个文件是 Python 识别一个文件夹是否是一个 Python 的重要标志。 举个例子,现在有如下的目录树,demo 及子文件夹 foo 和 bar 下都有 `__init__.py` 文件。 ```shell $ tree demo/ demo/ ├── bar │   └── __init__.py ├── foo │   └── __init__.py └── __init__.py ``` 在该目录下进入 Python Console 模式,然后就可以正常导入了 ```python >>> import demo >>> import demo.bar >>> import demo.foo ``` 如果此时我把 demo 目录下的 `__init__.py` 删除 ```python $ tree demo/ demo/ ├── bar │   └── __init__.py └── foo └── __init__.py ``` 再导入就会报错。 ```python >>> import demo Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named demo ``` ## 2. 命名空间包 在 Python 3.3 之后([PEP 420](https://www.python.org/dev/peps/pep-0420/)),即使一个文件夹中没有定义 `__init__.py`,也是可以被导入的,只不过它不是以 Python 包的形式导入,而是以命名空间包 (Namespace package) 的形式被导入,而这一特性是在 Python 3.3 被引入的。 比如还是上面的目录结构: ```shell $ tree demo/ demo/ ├── bar │   └── __init__.py └── foo └── __init__.py ``` 在 Python 3 下进入 Python Console 模式,发现导入是正常的 ```python >>> import demo >>> import demo.foo >>> import demo.bar >>> ``` 使用 `__path__` 查看一下,发现 demo 不再是一个常规包了,而是一个 namespace package ```python >>> demo <module 'demo' (namespace)> >>> >>> demo.__path__ _NamespacePath(['/root/python/demo']) ``` ## 3. 空间命名包的好处 利用命名空间包这个技术,可以用来导入目录分散的代码。 比如有如下的目录树 ```shell $ tree . ├── xc-pkg │   └── demo │   └── foo │   └── __init__.py └── xm-pkg └── demo └── bar └── __init__.py ``` 在这 `xc-pkg` 和 `xm-pkg` 这两个目录里,都有着共同的命名空间 demo。这时候再导入这两个包的时候,发现这两个包被合并到一起了 ```python >>> import sys >>> sys.path.extend(['xm-pkg', 'xc-pkg']) >>> >>> import demo.foo >>> import demo.bar >>> demo <module 'demo' (namespace)> ``` 在这里工作的机制被称为`命名空间包`的一个特征。从本质上讲,`命名空间包`是一种特殊的封装设计,为合并不同的目录的代码到一个共同的命名空间。 `命名空间包`的关键是确保顶级目录中没有 `__init__.py` 文件来作为共同的命名空间。缺失 `__init__.py` 文件使得在导入包的时候会发生有趣的事情:这并没有产生错误,解释器创建了一个由所有包含匹配包名的目录组成的列表。特殊的包命名空间模块被创建,只读的目录列表副本被存储在其 `__path__` 变量中。 ```python >>> demo.__path__ _NamespacePath(['xm-pkg/demo', 'xc-pkg/demo']) ``` 一个包是否被作为一个包命名空间的主要方法是检查其 `__file__` 属性。如果没有,那包是个命名空间。这也可以由其字符表现形式中的 namespace 这个词体现出来。 ```python >>> demo <module 'demo' (namespace)> >>> >>> demo.__file__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'demo' has no attribute '__file__' >>> ```