一、Python 项目打包
| 概念 | 说明 |
|---|
| PyPI | Python 包索引,官方仓库(pypi.org) |
| 分发格式 | 源码分发包(.tar.gz)和 Wheel 包(.whl) |
setup.py / pyproject.toml | 项目的配置文件,描述元数据和依赖 |
pip install | 从 PyPI 或本地安装包 |
二、项目结构
my_package/
├── src/ # 源码目录
│ └── my_package/ # 包目录
│ ├── __init__.py
│ ├── module_a.py
│ └── module_b.py
├── pyproject.toml # 项目配置(推荐)
├── setup.py # 传统配置文件
├── README.md
├── LICENSE
└── requirements.txt
三、配置 pyproject.toml
pyproject.toml 示例
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
version = "0.1.0"
description = "一个示例Python包"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "张三", email = "zhangsan@example.com"}
]
maintainers = [
{name = "李四", email = "lisi@example.com"}
]
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
keywords = ["example", "demo"]
dependencies = [
"requests>=2.28.0",
"numpy>=1.24.0",
"pandas>=1.5.0",
]
optional-dependencies = {
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
"ruff>=0.0.260",
],
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]
}
[project.urls]
homepage = "https://github.com/zhangsan/my_package"
repository = "https://github.com/zhangsan/my_package.git"
documentation = "https://my_package.readthedocs.io"
[project.scripts]
my-cli = "my_package.__main__:main"
[project.gui-scripts]
my-gui = "my_package.gui:main"
[tool.setuptools]
package-dir = {"" = "src"}
packages = ["my_package"]
[tool.setuptools.package-data]
my_package = ["*.txt", "*.json", "data/*"]
pyproject.toml 核心字段
| 字段 | 说明 | 必需 |
|---|
[project] | 项目元数据 | 必需 |
name | 包名(唯一标识,只能包含字母、数字、_、-) | 必需 |
version | 版本号(遵循 PEP 440) | 必需 |
description | 简短描述 | 必需 |
dependencies | 运行时依赖 | |
requires-python | Python 版本要求 | |
[project.scripts] | 命令行入口点 | |
四、使用 setup.py(传统方式)
# setup.py
from setuptools import setup, find_packages
setup(
name="my_package",
version="0.1.0",
author="张三",
author_email="zhangsan@example.com",
description="一个示例Python包",
long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
url="https://github.com/zhangsan/my_package",
packages=find_packages("src"),
package_dir={"": "src"},
package_data={
"my_package": ["*.txt", "*.json", "data/*"],
},
install_requires=[
"requests>=2.28.0",
"numpy>=1.24.0",
],
extras_require={
"dev": ["pytest>=7.0.0", "black>=23.0.0"],
"test": ["pytest>=7.0.0", "pytest-cov>=4.0.0"],
},
entry_points={
"console_scripts": [
"my-cli=my_package.__main__:main",
],
"gui_scripts": [
"my-gui=my_package.gui:main",
],
},
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"License :: OSI Approved :: MIT License",
],
python_requires=">=3.8",
)
setup.py vs pyproject.toml
| 特性 | setup.py | pyproject.toml |
|---|
| 语法 | Python 代码 | TOML 配置 |
| 可读性 | 一般 | 高 |
| 动态配置 | 支持 | 有限支持 |
| 推荐度 | 传统 | 现代推荐 |
五、安装和打包命令
1. 本地开发安装(可编辑模式)
# 在项目根目录下
pip install -e .
# 安装额外依赖
pip install -e .[dev,test]
# 使用 uv(更快)
uv pip install -e .
2. 构建包
# 安装构建工具
pip install build
# 构建分发包
python -m build
# 生成文件
# dist/
# my_package-0.1.0-py3-none-any.whl # Wheel 包
# my_package-0.1.0.tar.gz # 源码包
# 使用 uv 构建
uv build
3. 本地安装包
# 安装 Wheel 包
pip install dist/my_package-0.1.0-py3-none-any.whl
# 安装源码包
pip install dist/my_package-0.1.0.tar.gz
4. 发布到 PyPI
# 安装 twine
pip install twine
# 上传到 PyPI(测试环境)
twine upload --repository testpypi dist/*
# 上传到 PyPI(正式环境)
twine upload dist/*
# 使用 uv 发布
uv publish
六、版本管理
版本号规范(PEP 440)
主版本号.次版本号.补丁版本号[-预发布标签]
示例:
1.0.0 # 正式版
1.0.0-alpha # Alpha 版本
1.0.0-beta # Beta 版本
1.0.0-rc1 # 候选版本
版本号约束
| 语法 | 说明 | 示例 |
|---|
==1.0.0 | 精确版本 | requests==2.31.0 |
>=1.0.0 | 大于或等于 | numpy>=1.24.0 |
>=1.0.0,<2.0.0 | 版本范围 | pandas>=1.5.0,<2.0.0 |
~=1.0.0 | 兼容版本(>=1.0.0,<1.1.0) | flask~=2.3.0 |
* | 通配符 | django==4.* |
七、入口点和命令行工具
创建命令行工具
# src/my_package/__main__.py
def main():
print("这是 my-package 的命令行工具")
import sys
sys.exit(0)
if __name__ == "__main__":
main()
配置入口点
# pyproject.toml
[project.scripts]
my-cli = "my_package.__main__:main"
安装后可以直接运行:
my-cli
八、依赖管理
声明依赖
# pyproject.toml
dependencies = [
"requests>=2.28.0",
"numpy>=1.24.0",
"pandas>=1.5.0",
]
optional-dependencies = {
dev = ["pytest>=7.0.0", "black>=23.0.0"],
test = ["pytest>=7.0.0", "pytest-cov>=4.0.0"],
}
使用 requirements.txt
# requirements.txt
requests>=2.28.0
numpy>=1.24.0
pandas>=1.5.0
pip install -r requirements.txt
九、完整示例
项目结构
example_package/
├── src/
│ └── example_package/
│ ├── __init__.py
│ ├── core.py
│ └── __main__.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore
打包并发布
# 1. 清理旧构建文件
rm -rf dist/ build/ *.egg-info
# 2. 构建包
python -m build
# 3. 检查包
twine check dist/*
# 4. 上传到测试 PyPI
twine upload --repository testpypi dist/*
# 5. 从测试 PyPI 安装
pip install --index-url https://test.pypi.org/simple/ example-package
# 6. 上传到正式 PyPI
twine upload dist/*
十、常用工具
| 工具 | 用途 | 特点 |
|---|
pip | 安装包 | 最常用 |
uv | 安装包(替代 pip) | 速度极快,兼容 pip |
build | 构建包 | 标准构建工具 |
twine | 上传包 | 安全上传到 PyPI |
setuptools | 打包配置 | 传统工具 |
poetry | 项目管理 | 一体化依赖管理 |
hatch | 项目管理 | 现代 Python 项目管理工具 |