在Python生态中,PyPI(Python Package Index)就像是一个巨大的工具箱,里面有超过40万个现成的工具供我们使用。但当你开发出一个值得分享的解决方案时,如何把它变成别人也能pip install的规范包?这是我十年前第一次尝试打包时遇到的困惑。
记得当时我写了个自动化处理CSV文件的小工具,团队里其他成员想用,我只能让他们复制粘贴代码文件。直到有一天同事问我:"为什么不能像用requests库那样直接pip安装?"这个问题让我踏上了学习Python打包的旅程。
一个规范的PyPI包目录结构应该像这样:
code复制my_awesome_package/
├── my_awesome_package/ # 主包目录
│ ├── __init__.py # 包初始化文件
│ ├── module1.py # 模块文件
│ └── module2.py
├── tests/ # 测试目录
├── docs/ # 文档目录
├── setup.py # 构建配置文件
├── pyproject.toml # 现代构建配置
├── README.md # 项目说明
└── LICENSE # 许可证文件
关键点在于__init__.py文件,它让Python将这个目录识别为可导入的包。即使是个空文件,它的存在也标志着这是一个Python包。
setup.py是打包的核心配置文件,一个完整的示例如下:
python复制from setuptools import setup, find_packages
setup(
name="my_awesome_package",
version="0.1.0",
author="Your Name",
author_email="your.email@example.com",
description="A short description of your package",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
url="https://github.com/yourusername/my_awesome_package",
packages=find_packages(),
install_requires=[
'requests>=2.25.1',
'numpy>=1.20.0',
],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)
特别注意install_requires参数,它定义了包的依赖关系。我强烈建议在这里指定最低版本要求,避免用户环境中的版本冲突。
传统上我们使用setuptools,但现在推荐使用PEP 517/518标准工具链:
bash复制python -m pip install --upgrade build twine
python -m build
这个命令会在dist/目录生成两个文件:
.tar.gz源码包.whl构建分发文件经验之谈:wheel格式(.whl)的安装速度比源码包快10倍以上,特别是包含C扩展时。确保你的包同时提供这两种格式。
我推荐语义化版本(SemVer):
在__init__.py中定义版本号:
python复制__version__ = "1.3.0"
然后在setup.py中动态读取:
python复制from my_awesome_package import __version__
setup(
version=__version__,
# ...
)
正式发布前,先用TestPyPI验证:
bash复制python -m twine upload --repository testpypi dist/*
系统会提示输入用户名和密码。注意:TestPyPI需要单独注册账号。
验证无误后,正式发布:
bash复制python -m twine upload dist/*
安全提示:永远不要在代码中硬编码PyPI凭证!推荐使用
~/.pypirc文件或环境变量。
如果你的包需要数据文件(如模板、配置文件),需要在MANIFEST.in中声明:
code复制include LICENSE
recursive-include my_awesome_package/templates *.html
recursive-include my_awesome_package/data *.json
然后在setup.py中设置:
python复制setup(
include_package_data=True,
# ...
)
遇到"platform wheel"问题时,可以使用环境标记:
python复制setup(
install_requires=[
'pywin32 >= 1.0; platform_system == "Windows"',
'pyobjc >= 6.0; platform_system == "Darwin"',
]
)
上传失败:HTTPError 403
安装时报模块不存在
__init__.py文件是否存在packages参数正确包含所有子包版本冲突
pip check验证依赖关系每次发布新版本时:
__version__bash复制pip install .
bash复制git tag v1.2.0
git push --tags
我习惯在项目根目录放一个release.sh脚本自动化这个过程:
bash复制#!/bin/bash
set -e
# 检查干净的工作目录
if [[ -n $(git status -s) ]]; then
echo "工作目录不干净,请先提交更改"
exit 1
fi
# 提取新版本号
VERSION=$(python -c "from my_awesome_package import __version__; print(__version__)")
# 构建
python -m build
# 创建git标签
git tag -a "v${VERSION}" -m "Version ${VERSION}"
git push --tags
# 上传到PyPI
python -m twine upload dist/*
echo "版本 ${VERSION} 发布成功!"
使用Sphinx生成专业文档:
bash复制pip install sphinx
sphinx-quickstart docs
在docs/source/conf.py中添加:
python复制import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
project = 'My Awesome Package'
copyright = '2023, Your Name'
author = 'Your Name'
release = __import__('my_awesome_package').__version__
在setup.py中添加测试依赖:
python复制setup(
extras_require={
'test': [
'pytest>=6.0',
'pytest-cov>=2.0',
],
},
)
然后用户可以这样安装测试依赖:
bash复制pip install my_awesome_package[test]
在.github/workflows/test.yml中添加:
yaml复制name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[test]
- name: Test with pytest
run: |
pytest --cov=my_awesome_package tests/
发布第一个版本只是开始。一个健康的Python包需要:
pip outdated检查过期的依赖我维护的一个包在过去三年里经历了17次版本更新,每次更新都严格遵循这个流程。记住:用户信任建立在可靠性和一致性上。