distutils 是 Python 的一个标准库模块,用于构建和分发 Python 模块,它的主要目标是让开发者能够轻松地将自己的 Python 代码打包成标准的分发包(如 .tar.gz 或 .zip 文件),以便其他用户可以轻松地安装。

distutils 的核心概念
在使用 distutils 之前,需要理解几个核心概念:
- Setup Script: 一个名为
setup.py的 Python 脚本,这是整个打包过程的入口和配置文件,它告诉distutils关于你的项目(如名称、版本、作者、依赖等)以及如何构建它。 - Distribution: 指的是你打包好的最终产物,例如一个源码分发包(
.tar.gz)或一个二进制分发包(.zip或.whl)。 - Module/Package: 你想要分发的 Python 代码本身,一个模块是一个
.py文件,一个包是一个包含__init__.py文件的目录。 - Metadata: 关于你项目的描述信息,如名称、版本、作者、邮箱、许可证等,这些信息会写入生成的分发包中。
创建一个简单的 setup.py 文件
这是使用 distutils 的第一步,我们从一个最简单的例子开始。
假设你有一个简单的项目结构:
my_project/
├── my_module/
│ ├── __init__.py
│ └── hello.py
└── setup.py
my_module/hello.py 的内容如下:

# my_module/hello.py
def say_hello(name):
"""A simple function to say hello."""
print(f"Hello, {name}!")
my_module/__init__.py 的内容如下(使其成为一个包):
# my_module/__init__.py from .hello import say_hello
我们来创建 setup.py 文件:
# setup.py
from distutils.core import setup
setup(
name="MySimpleModule",
version="0.1.0",
description="A very simple Python module example.",
author="Your Name",
author_email="your.email@example.com",
packages=["my_module"],
)
setup() 函数参数详解:
name: 分发包的名称。version: 分发包的版本号。description: 项目的简短描述。author: 作者姓名。author_email: 作者邮箱。packages: 一个列表,包含了所有需要分发的包。distutils会自动在项目目录下查找这些包,这里我们指定了my_module。
常用的 setup() 参数
setup() 函数有很多参数,下面是一些最常用的:

1. 项目信息
long_description: 项目的详细描述,通常从README.md或README.rst文件中读取,在 PyPI 上显示时非常有用。url: 项目的主页或代码仓库 URL。license: 项目的许可证,如 "MIT", "Apache 2.0", "GPLv3"。
示例:
import os
from distutils.core import setup
# 读取 README 文件内容作为 long_description
this_directory = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
setup(
name="MySimpleModule",
version="0.1.0",
description="A very simple Python module example.",
long_description=long_description,
long_description_content_type="text/markdown", # 如果是 Markdown 格式
url="https://github.com/yourusername/my_project",
author="Your Name",
author_email="your.email@example.com",
license="MIT",
packages=["my_module"],
)
2. 依赖管理
install_requires: 一个列表,指定了你的项目在运行时依赖的其他库,当用户安装你的包时,pip会自动安装这些依赖。
示例:
setup(
# ... 其他参数 ...
install_requires=[
"requests >= 2.20.0",
"numpy >= 1.15.0",
"Pillow", # 没有版本号表示安装最新版本
],
)
3. 包含非 Python 文件
默认情况下,distutils 只会包含 Python 文件,如果你需要包含其他文件(如配置文件、数据文件、C 源码等),可以使用以下参数:
package_data: 指定需要包含在特定包内的数据文件。data_files: 指定需要安装到特定位置的数据文件(不一定是包内)。
package_data 示例:
假设你的项目结构如下:
my_project/
├── my_module/
│ ├── __init__.py
│ └── hello.py
│ └── data/
│ └── config.json
└── setup.py
你想把 config.json 和 my_module/data/ 目录下的所有 .txt 文件一起打包:
from distutils.core import setup
setup(
# ... 其他参数 ...
packages=["my_module"],
package_data={
'my_module': ['data/config.json', 'data/*.txt'],
},
)
运行 setup.py 命令
在终端中,进入 my_project 目录,就可以对 setup.py 执行各种命令了。
1. 构建分发包
这是最常用的命令,它会创建一个 dist/ 目录,里面包含源码分发包(.tar.gz)。
python setup.py sdist
执行后,你会看到:
running sdist
running check
...
writing list of files to ...
creating dist
...
creating dist/MySimpleModule-0.1.0.zip
...
dist/ 目录下会生成 MySimpleModule-0.1.0.zip 和 MySimpleModule-0.1.0.tar.gz。
2. 安装包
你可以将你刚创建的分发包安装到当前的 Python 环境中。
# 从分发包安装 pip install dist/MySimpleModule-0.1.0.tar.gz # 或者直接使用 setup.py 安装(不推荐用于生产,但方便测试) python setup.py install
安装后,你就可以在 Python 代码中导入并使用你的模块了:
>>> import my_module
>>> my_module.say_hello("World")
Hello, World!
3. 其他常用命令
python setup.py --help: 显示所有可用的命令及其帮助信息。python setup.py --help-commands: 显示所有可用的构建命令。python setup.py build: 只构建,不创建分发包,通常在sdist或bdist命令内部调用。python setup.py clean: 清理构建过程中产生的临时文件(如build/目录)。
distutils vs. setuptools vs. distutils2 / packaging
这是一个非常重要的历史和概念辨析。
| 工具/库 | 状态 | 关系和用途 |
|---|---|---|
distutils |
标准库,已过时 | Python 自带的打包工具,功能有限,不支持依赖管理、现代的构建后端(如 setuptools 的 bdist_wheel)等。新项目不应再单独使用它。 |
setuptools |
事实标准,强烈推荐 | distutils 的超集,它扩展了 distutils 的功能,并修复了其许多问题。现代 Python 项目开发的首选,它引入了 find_packages()、install_requires、entry_points、wheel 等关键功能。 |
distutils2 |
已废弃 | 最初是 distutils 的继任者,旨在成为标准库的一部分,但最终项目停滞,其功能被 setuptools 和 packaging 等库吸收。 |
packaging |
现代标准库,用于元数据 | 一个专注于处理 Python 包元数据和规范的库(如版本号、环境标记),它本身不用于构建,而是被 setuptools、pip、wheel 等工具广泛使用。 |
对于任何新的 Python 项目,请直接使用
setuptools,而不是distutils。
setuptools 的 setup.py 与 distutils 的用法基本兼容,只是提供了更多强大的功能,你可以用 setuptools 的 find_packages() 自动发现所有包:
# 使用 setuptools 的写法
from setuptools import setup, find_packages
setup(
name="MyProject",
version="0.1.0",
packages=find_packages(), # 自动查找所有包
install_requires=[
"requests",
],
)
现代替代方案:pyproject.toml 和 build 工具
尽管 setup.py + setuptools 仍然是主流,但 Python 社区正在向一种更现代、更标准化的构建方式过渡。
pyproject.toml: 一个新的配置文件标准,用于项目的构建系统、依赖和元数据,它取代了setup.py中大量的配置信息。build工具: 一个新的、标准的 Python 包构建工具,它不关心你的项目是如何构建的(是setuptools、flit还是poetry),它只负责读取pyproject.toml并构建出符合标准的分发包(.whl和.tar.gz)。
为什么这是一个进步?
- 标准化: 所有项目都使用相同的配置文件格式 (
pyproject.toml),消除了对setup.py脚本安全性的担忧(setup.py是以 Python 代码执行的,可能包含恶意代码)。 - 分离: 构建配置 (
pyproject.toml) 和构建逻辑(setuptools的内部机制)分离开,更清晰。 - 简化: 对于简单的项目,使用像
flit或poetry这样的工具,你几乎不需要写任何setup.py代码。
如何使用 build 工具?
-
安装
build:pip install build
-
创建
pyproject.toml: 在你的项目根目录下创建一个pyproject.toml文件。setuptools也支持使用它来配置项目。# pyproject.toml [build-system] requires = ["setuptools>=45", "wheel"] build-backend = "setuptools.build_meta" [project] name = "MySimpleModule" version = "0.1.0" description = "A very simple Python module example." authors = [ {name = "Your Name", email = "your.email@example.com"}, ] license = {text = "MIT"} readme = "README.md" requires-python = ">=3.7" dependencies = [ "requests >= 2.20.0", ] [tool.setuptools] packages = ["my_module"] -
构建: 你不再需要运行
python setup.py sdist,而是运行:python -m build
这会读取
pyproject.toml,并使用setuptools在后台完成构建,最终在dist/目录生成分发包。
总结与建议
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 学习 Python 打包基础 | distutils |
理解核心概念(setup.py, sdist 等)的起点。 |
| 维护旧项目 | distutils / setuptools |
如果项目已经使用 distutils,可以继续维护,但建议迁移到 setuptools 以获得更多功能。 |
| 新项目开发 | setuptools + pyproject.toml |
目前最主流、最平衡的选择,功能强大,社区支持好,并且正在向 pyproject.toml 标准过渡。 |
| 追求极简配置的新项目 | flit 或 poetry |
如果你的项目非常简单,不想写复杂的配置,flit 是一个极好的选择,它只需要很少的配置。poetry 则是一个集成了依赖管理和打包的“一站式”工具。 |
| 遵循最新标准 | build 工具 + pyproject.toml |
这是未来的方向,无论你使用 setuptools, flit 还是 poetry,最终都推荐使用 python -m build 来构建你的包。 |
对于绝大多数开发者来说,从 setuptools 和 pyproject.toml 开始学习是当前的最佳实践,但理解 distutils 的工作原理对于阅读旧项目代码和打包历史仍然非常有价值。
