Python 包,常规包,命名空间包介绍

我被代码海扁署名-非商业-禁演绎
阅读 8:09·字数 2449·发布 
Bilibili 空间
关注 960

本节所讲述的内容,并不完全适用于通过类ModuleType动态创建的表示包的模块类型对象。

Python 包

Python 包是一类特殊的模块,他并不对应同名的py脚本文件,而是对应同名的文件夹。

如何判断 Python 模块是否为包?

在代码层面,如果一个模块具有__path__特性,则该模块为 Python 包。__path__特性可用于指示 Python 包对应的文件夹路径。

在 Python 的交互模式中,我们尝试查看模块reos__path__特性。从输出结果可以看出,模块re是包,模块os不是包。

Windows
import re
re.__path__
['\\python312.zip\\re']
import os
os.__path__

AttributeError: module 'os' has no attribute '__path__'. Did you mean: '__all__'?
UNIX/Linux/macOS
import re
re.__path__
['/usr/lib/python3.11/re']
import os
os.__path__

AttributeError: module 'os' has no attribute '__path__'. Did you mean: '__all__'?

如何命名 Python 包?

Python 包的名称默认与其对应的文件夹名称相同,并不需要特意的命名。比如,一个名称为workers的文件夹,其对应的包名称为workers

应该称呼 Python 模块还是 Python 包?

由于包是一种特殊的模块,因此,称呼某个包为模块并不算错误,比如,re模块。

模块

如果你需要了解 Python 模块,可以查看Python 模块完全限定名,模块缓存介绍一节。

Python 常规包

Python 常规包对应的文件夹,拥有一个名为__init__.py的文件,他相当于模块对应的同名py文件,包含包的相关代码。

Python 常规包的存储形式

在编写代码期间,Python 常规包存储为文件夹和文件夹中__init__.py文件。但编译后,常规包的相关代码会转变为字节码并存储在pyc文件中。

这里,包hero对应的文件夹包含__init__.py,因此hero是一个常规包。

hero/__init__.py
# hero 是一个常规包
print('我是常规包 hero!')

Python 命名空间包

如果包对应的文件夹未包含名为__init__.py的文件,则该包被称为命名空间包。

命名空间包和常规包之间的区别

事实上,常规包和命名空间包都具有命名空间的效果,在不同的常规包或命名空间包中,你可以定义名称相同的内容。至于区别,除了是否包含文件__init__.py,还有以下几点。

命名空间包的__file__特性为None,而常规包为__init__.py文件的路径。命名空间包的__path__特性为_NamespacePath类型,而常规包为列表。命名空间包的__loader__特性为NamespaceLoader类型,而常规包为SourceFileLoader类型。

Python 模块的 __loader__ 特性

Python 模块的__loader__特性表示用于加载该模块的加载器。

这里,我们新建一个空的文件夹enemies,他将对应命名空间包。切换命令行至enemies的上级文件夹,启动 Python 交互模式并使用import进行导入操作,然后查看enemies包的相关信息。

Windows
import enemies
enemies.__file__ == None
True
enemies.__path__
_NamespacePath(['\\packages\\enemies'])
enemies.__loader__
<_frozen_importlib_external.NamespaceLoader object at>
UNIX/Linux/macOS
import enemies
enemies.__file__ == None
True
enemies.__path__
_NamespacePath(['/packages/enemies'])
enemies.__loader__
<_frozen_importlib_external.NamespaceLoader object at>

命名空间,作用域

要了解什么是命名空间,可以查看编程教程命名空间,作用域介绍一节。

zip 文件中的 Python 包

zip文件中,Python 包只能以常规包的形式存在,他们应该具有脚本文件__init__.py,否则将无法被导入。

在下面的示例中,压缩文件plants.zip含有常规包flowers,与plants.zip处于同一目录的脚本文件my_plants.py,会尝试导入zip中的flowers包。启动命令行并切换至文件my_plants.py所在的目录,运行后可以看到相关的输出结果。

plants/flowers/__init__.py
# flowers 是一个常规包
print('我是包 flowers!')
my_plants.py
# 获取压缩文件 plants.zip 的绝对路径,并添加至模块搜索路径
import os
import sys
zip_path = os.path.abspath('plants.zip')
sys.path.append(zip_path)

# 导入 plants.zip 中的 flowers 包 import flowers
我是包 flowers!

如果将plants.zip中的脚本文件__init__.py删除,那么重复上述执行步骤会引发异常ModuleNotFoundError

Python 子模块和子包

Python 包对应的文件夹中的py文件或子文件夹,即为该包的子模块和子包,子包可以是常规包,也可以是命名空间包。

Python 模块和包的执行优先级

虽然这并不符合规范,但处于同一位置的 Python 模块和包可以具有相同的名称。在这种情况下,导入操作将按照如下优先顺序进行,常规包的优先级最高,模块次之,命名空间包的优先级最低。当同名模块或包中的一个被导入后,另一个将被忽略。

在包school中,存在子包homework和子模块homework,其中子包homework是一个常规包。在脚本文件my_school.py(与school包对应的文件夹处于同一目录)中,表达式import school.homework导入的是常规包homework,而不是模块homework

school/homework/__init__.py
print('功课太多了!')
school/homework.py
print('这里是模块 homework!')
my_school.py
# 导入包 school 的子包 homework,他是一个常规包
import school.homework
功课太多了!

如果把文件夹homework中的脚本文件__init__.py删除,那么常规包homework会变为命名空间包,再次运行my_school.py将导入模块homework

这里是模块 homework!

内容分类

源码

hero/__init__.py·codebeatme/python-reference·GitHub
plants/flowers/__init__.py·codebeatme/python-reference·GitHub
my_plants.py·codebeatme/python-reference·GitHub
school/homework/__init__.py·codebeatme/python-reference·GitHub
school/homework.py·codebeatme/python-reference·GitHub
my_school.py·codebeatme/python-reference·GitHub