我也想用 pip下载安装我的代码
pip install的包是如何创建的呢
前言
本文基于现代 Python 打包生态(PEP 621 / PEP 517/518、pyproject.toml 为中心),并同时给出 setup.py 的最小示例以兼容老项目或需要程序化构建的场景。有关 pyproject.toml 的规范与 build-system 的行为,请参阅官方规范与 PEP 文档。(packaging.python.org)
最小项目结构(示例)
1
2
3
4
5
6
7
8
9
10
your_project/
├── src/
│ └── your_package/
│ └── __init__.py
├── tests/
├── README.md
├── LICENSE
├── pyproject.toml
├── setup.py # 可选:仅当需要程序化步骤时保留
├── MANIFEST.in # 可选:包含额外资源
说明:推荐使用 src/ 布局(能减少同名包调试问题),把可读文档放在 README.md,并在 pyproject.toml 中声明元数据。有关如何书写 pyproject.toml 的指南见官方 Packaging 文档。(packaging.python.org)
pyproject.toml — 完整示例与逐行解释
示例文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "your-package-name"
version = "0.1.0"
description = "A short description of the package."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.8"
authors = [{ name = "Your Name", email = "you@example.com" }]
license = { text = "MIT" }
dependencies = [
"requests>=2.25",
"numpy>=1.21"
]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent"
]
[project.optional-dependencies]
dev = ["pytest", "black", "sphinx"]
[project.scripts]
your-cmd = "your_package.cli:main"
下面逐段解释该文件中每一项的含义与常见写法。
【build-system】
1
2
3
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
requires:构建时需要安装的 package 列表(pip 在构建时会先安装这些依赖)。常见值是setuptools+wheel(也可能包含build、flit等,取决于你选的后端)。这个字段由 PEP 517/518 定义,用来告诉工具在构建前需要哪些依赖。(Python Enhancement Proposals (PEPs))build-backend:指定构建后端(back-end),例如setuptools.build_meta,或者flit_core.buildapi、poetry.core.masonry.api等。后端负责把源码打包成 sdist/wheel。(Python Enhancement Proposals (PEPs))
通常来说我都闭眼直接复制了。
【project】
该表遵循 PEP 621 的 metadata 规范(即把包的“元数据”声明在 pyproject.toml 的 [project] 中)。(Python Enhancement Proposals (PEPs))
name:包的分发名(在 PyPI 上显示并用于pip install name)。注意命名规范(小写、短横线通常被接受),并在发布前确认是否被占用。version:遵循 PEP 440 的版本号(语义化或你自己的策略)。也可以用dynamic = ["version"]并由后端在构建时填充(例如基于 git tag)。(packaging.python.org)description:一句话描述(短摘要)。readme:可以是简单的字符串路径或带file/content-type的表,现代工具将把 README 内容作为 long description 展示。示例里用了{ file = "README.md", content-type = "text/markdown" }。(packaging.python.org)requires-python:指定 Python 版本兼容性(例如>=3.8)。pip和安装索引会据此拒绝不满足的环境。authors/maintainers:作者/维护者列表,使用数组表形式(包含 name/email)。license:可以用{ text = "MIT" }或{ file = "LICENSE" }(把许可证文本或文件声明出来)。不同后端对这两种形式的处理细节略有差别。(packaging.python.org)dependencies:主依赖,写法为 PEP 508 风格的字符串(例如"requests>=2.25")。构建后的元数据会把这些信息写入METADATA。(packaging.python.org)classifiers:PyPI 上的分类(帮助他人检索),例如 Python 版本 / License / OS。urls:可选的 homepage / repository / documentation 链接(用于 PyPI 展示)。
entry points / console scripts
1
2
[project.scripts]
your-cmd = "your_package.cli:main"
- 以上表示:安装包时会创建一个可执行命令
your-cmd,其调用目标为your_package.cli模块中的main函数。
闭眼复制…
optional-dependencies / dynamic 等说明
[project.optional-dependencies]:声明可选依赖组(例如dev、docs、tests),便于用户pip install your-package[dev]。dynamic:如果某些字段在打包前由脚本/后端动态生成(例如version),需要在[project]中列出dynamic = ["version"]并确保后端支持。(packaging.python.org)
setup.py — 示例与说明
为什么现代项目倾向于 pyproject.toml?
PEP 517/621 推动 metadata 与构建配置向 pyproject.toml 集中,减少对 setup.py 的依赖。大多数情况下,声明式配置(pyproject.toml、setup.cfg)已足够。若你需要程序化步骤(比如运行 C 语言扩展的自定义编译逻辑、在打包时运行自定义 Python 代码来生成文件等),才保留 setup.py。官方 setuptools 文档也建议新项目尽量使用声明式配置,仅在必要时保留 setup.py。(setuptools.pypa.io)
minimal setup.py 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# setup.py (最小示例)
from setuptools import setup, find_packages
from pathlib import Path
README = Path(__file__).parent.joinpath("README.md").read_text(encoding="utf-8")
setup(
name="your-package-name",
version="0.1.0",
description="A short description of the package.",
long_description=README,
long_description_content_type="text/markdown",
author="Your Name",
author_email="you@example.com",
packages=find_packages(where="src"),
package_dir={"": "src"},
include_package_data=True,
python_requires=">=3.8",
install_requires=[
"requests>=2.25",
],
entry_points={
"console_scripts": [
"your-cmd=your_package.cli:main",
]
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)
find_packages(where="src")+package_dir配合src/布局使用。include_package_data=True会把MANIFEST.in指定的资源包含进 sdist。- 如果只是声明性元数据,优先把这些字段放到
pyproject.toml/setup.cfg,setup.py保留最小 stub 或程序化逻辑。(setuptools.pypa.io)
MANIFEST.in 示例
如果你的包需要包含配置文件、模板、数据文件等非 Python 资源,可以创建 MANIFEST.in 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# MANIFEST.in 示例
include README.md
include LICENSE
include CHANGELOG.md
include requirements.txt
# 包含所有配置文件
include *.cfg
include *.ini
include *.yaml
include *.yml
# 包含特定目录下的文件
recursive-include your_package/templates *
recursive-include your_package/static *
recursive-include your_package/data *.json *.csv *.txt
# 排除不需要的文件
global-exclude *.pyc
global-exclude *.pyo
global-exclude __pycache__
global-exclude .DS_Store
global-exclude *.so
prune tests/
prune docs/
在 pyproject.toml 中启用:
1
2
[tool.setuptools]
include-package-data = true
或在 setup.py 中:
1
2
3
4
setup(
# ... 其他参数
include_package_data=True,
)
打包与上传(命令汇总)
安装构建工具:
1
pip install --upgrade build
在项目根目录生成分发包(sdist + wheel):
1
python -m build生成结果放到
dist/文件夹。 (工具会读取pyproject.toml的[build-system]指令来安装构建依赖并调用后端进行构建。)(Python Enhancement Proposals (PEPs))安装上传工具(Twine):
1
pip install --upgrade twine
推荐先上传到 TestPyPI 测试(验证元数据/README 渲染/包名等):
1
twine upload --repository testpypi dist/*
在 TestPyPI 上确认包的显示与安装行为正确后,再上传正式 PyPI。(packaging.python.org)
上传到正式 PyPI:
1
twine upload dist/*关于 PyPI API token:在 PyPI 网站生成 token 后,上传时将用户名设为
__token__,密码设为 token(包含pypi-前缀),或在 CI 中把它作为TWINE_USERNAME/TWINE_PASSWORD或在~/.pypirc中配置。推荐在 CI 使用加密 secrets(不把 token 明文放到仓库)。(PyPI)
常见错误与排查
- 包名已被占用 / 403 或 name collision:在发布前先在 TestPyPI 或 PyPI 搜索包名,确认未被占用。若被占用只能换名或联系包名拥有者。
- long_description 在 PyPI 上渲染失败:确保
readme/long_description_content_type正确(text/markdown)并在本地用twine check dist/*检查元数据。 - twine 上传认证失败 (403 / 401):通常是 token/凭据问题。建议在 PyPI 控制台生成 API token,并按官方说明把用户名设为
__token__。(PyPI) - 构建失败找不到后端/依赖:确认
[build-system].requires中包含了构建所需的包(例如setuptools、wheel),并在本地或 CI 里升级到兼容的版本。(Python Enhancement Proposals (PEPs)) - 包含非 Python 文件(模板/数据)缺失:使用
MANIFEST.in+include_package_data=True把额外资源包含进 sdist。参考 setuptools 文档。(setuptools.pypa.io)