Python 包,常规包,命名空间包介绍
本节所讲述的内容,并不完全适用于通过类ModuleType
动态创建的表示包的模块类型对象。
Python 包
Python 包是一类特殊的模块,他并不对应同名的py
脚本文件,而是对应同名的文件夹。
如何判断 Python 模块是否为包?
在代码层面,如果一个模块具有__path__
特性,则该模块为 Python 包。__path__
特性可用于指示 Python 包对应的文件夹路径。
在 Python 的交互模式中,我们尝试查看模块re
和os
的__path__
特性。从输出结果可以看出,模块re
是包,模块os
不是包。
import re
re.__path__
['…\\python312.zip\\re']
import os
os.__path__
…
AttributeError: module 'os' has no attribute '__path__'. Did you mean: '__all__'?
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 是一个常规包
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
包的相关信息。
import enemies
enemies.__file__ == None
True
enemies.__path__
_NamespacePath(['…\\packages\\enemies'])
enemies.__loader__
<_frozen_importlib_external.NamespaceLoader object at…>
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
所在的目录,运行后可以看到相关的输出结果。
# flowers 是一个常规包
print('我是包 flowers!')
# 获取压缩文件 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
。
print('功课太多了!')
print('这里是模块 homework!')
# 导入包 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