HOWTO · Python

Python 中的包与模块

本教程演示了 python 中模块和包之间的区别。

本页内容

Python 使用模块和包将代码划分为可导入的单元。模块通常是一个 .py 文件;包则将多个模块归在同一个名称下。了解不同导入形式会绑定哪些名称,有助于选择合适的代码结构,也能避免误以为导入包时会加载它的所有子模块。

什么是 Python 模块?

模块是可导入的 Python 代码单元,通常是包含函数、类或值的 .py 文件。例如,名为 greetings.py 的文件可以定义可复用的 greet 函数。其他代码可以导入该模块并调用 greetings.greet(...)。模块名取自不带 .py 扩展名的文件名,而该函数会作为模块的属性供代码访问。

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

from pathlib import Path
import sys
import tempfile

with tempfile.TemporaryDirectory() as folder:
    Path(folder, "greetings.py").write_text(
        'def greet(name):\n    return f"Hello, {name}!"\n', encoding="utf-8"
    )
    sys.path.insert(0, folder)
    import greetings
    print(greetings.greet("Ari"))

此示例会在临时目录中创建模块,因此无需依赖文章中的其他文件,就能作为单个文件运行。导入语句会绑定模块名 greetings;通过该名称即可访问其中的函数。

什么是 Python 包?

包会在目录层次结构中组织相关模块。常规包的目录中包含一个 __init__.py 文件。导入该包时会运行此文件;其中可以放置初始化代码,也可以显式公开选定的名称。该文件也可以为空。并非所有包都必须包含 __init__.py:Python 命名空间包可以分布在导入路径上的多个目录中,并且不包含此文件。

一个常见的常规包可能包含 my_package/__init__.py、my_package/reader.py 和 my_package/writer.py。这些模块可以各自承载专注于特定功能的代码,而包名则提供共同的导入路径。项目需要时,包还可以包含子包,形成更深的层次结构。

Python 如何导入模块和包

导入操作会加载指定的模块或包,并根据导入形式绑定相应名称。import greetings 会让 greetings 可用,而 from greetings import greet 则会直接绑定 greet。对于常规包,import my_package 会运行其 __init__.py;它不会自动导入目录中的每个模块。要使用子模块,需显式导入,例如 from my_package import reader,或者导入包初始化文件有意公开的名称。

点号分隔的名称也体现了这种区别。在 import xml.etree 中,Python 会加载指定的包路径,并在当前作用域绑定名称 xml;要访问子包,可使用 xml.etree。在 from xml import etree 中,则会直接绑定名称 etree。这两种形式都可以加载相关组件,但代码中可直接使用的名称不同。包初始化文件可以提供方便的公共接口,但不要假定只导入包本身后就能使用其中每个内部模块。

模块与包的主要区别

模块通常是单个文件;包则是以目录为基础的命名空间,可以组织多个模块和子包。模块为相关定义提供集中的位置。包将这些专注于特定功能的单元组织起来,并可以定义初始化行为或公共接口。它们的导入路径反映了各自的结构:独立模块可以通过 greetings 导入,而包中的模块可以通过 my_package.reader 导入。

包并不只是一个必须带有初始化文件的模块。常规包使用 __init__.py,而命名空间包不使用它。此外,导入包并不意味着所有子模块都会加载。显式导入能让依赖关系和可用名称更清楚。

何时使用模块或包

如果少量、职责明确的函数或类适合放在一起,而且不需要层次结构,就使用模块。如果相关职责已经扩展为多个模块、子包有助于划分不同领域,或者需要有意设计包级接口,就使用包。只要模块足够满足需要,就先从模块开始;当包结构能改善代码导航和复用时,再拆分为包。尽量明确地导入名称,让读者看得出每个名称来自何处。