URLhttps://learnscript.net/zh/python-reference/packages/
    复制链接转到说明  示例

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

    我被代码海扁署名-非商业-禁演绎
    阅读 8:04·字数 2421·发布 

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

    Python 包

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

    如何判断模块是否为包?

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

    在交互模式中,我们尝试查看模块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__'?

    如何命名包?

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

    应该称呼模块还是包?

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

    模块

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

    Python 常规包

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

    常规包的存储形式

    在编写代码期间,常规包存储为文件夹和文件夹中__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类型。

    模块的 __loader__ 特性

    模块的__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文件中,包只能以常规包的形式存在,他们应该具有脚本文件__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 子模块和子包

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

    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