python-packaging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePython Packaging
Python 包打包指南
Comprehensive guide to creating, structuring, and distributing Python packages using modern packaging tools, pyproject.toml, and publishing to PyPI.
本指南全面介绍如何使用现代打包工具、pyproject.toml创建、构建结构并分发Python包,以及发布至PyPI的流程。
When to Use This Skill
何时使用本技能
- Creating Python libraries for distribution
- Building command-line tools with entry points
- Publishing packages to PyPI or private repositories
- Setting up Python project structure
- Creating installable packages with dependencies
- Building wheels and source distributions
- Versioning and releasing Python packages
- Creating namespace packages
- Implementing package metadata and classifiers
- 创建用于分发的Python库
- 构建带有入口点的命令行工具
- 将包发布至PyPI或私有仓库
- 搭建Python项目结构
- 创建包含依赖的可安装包
- 构建wheel和源码分发包
- Python包的版本管理与发布
- 创建命名空间包
- 配置包元数据与分类器
Core Concepts
核心概念
1. Package Structure
1. 包结构
- Source layout: (recommended)
src/package_name/ - Flat layout: (simpler but less flexible)
package_name/ - Package metadata: pyproject.toml, setup.py, or setup.cfg
- Distribution formats: wheel (.whl) and source distribution (.tar.gz)
- 源码布局:(推荐)
src/package_name/ - 扁平布局:(更简单但灵活性较低)
package_name/ - 包元数据:pyproject.toml、setup.py 或 setup.cfg
- 分发格式:wheel(.whl)和源码分发包(.tar.gz)
2. Modern Packaging Standards
2. 现代打包标准
- PEP 517/518: Build system requirements
- PEP 621: Metadata in pyproject.toml
- PEP 660: Editable installs
- pyproject.toml: Single source of configuration
- PEP 517/518:构建系统要求
- PEP 621:pyproject.toml中的元数据规范
- PEP 660:可编辑安装
- pyproject.toml:单一配置源
3. Build Backends
3. 构建后端
- setuptools: Traditional, widely used
- hatchling: Modern, opinionated
- flit: Lightweight, for pure Python
- poetry: Dependency management + packaging
- setuptools:传统且广泛使用的工具
- hatchling:现代、约定式的构建工具
- flit:轻量级工具,适用于纯Python项目
- poetry:集依赖管理与打包于一体的工具
4. Distribution
4. 分发渠道
- PyPI: Python Package Index (public)
- TestPyPI: Testing before production
- Private repositories: JFrog, AWS CodeArtifact, etc.
- PyPI:Python包索引(公开)
- TestPyPI:生产环境发布前的测试环境
- 私有仓库:JFrog、AWS CodeArtifact等
Quick Start
快速开始
Minimal Package Structure
最小化包结构
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── module.py
└── tests/
└── test_module.pymy-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── module.py
└── tests/
└── test_module.pyMinimal pyproject.toml
最小化pyproject.toml配置
toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
"requests>=2.28.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=22.0",
]toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
"requests>=2.28.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=22.0",
]Package Structure Patterns
包结构模式
Pattern 1: Source Layout (Recommended)
模式1:源码布局(推荐)
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ ├── utils.py
│ └── py.typed # For type hints
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_utils.py
└── docs/
└── index.mdAdvantages:
- Prevents accidentally importing from source
- Cleaner test imports
- Better isolation
pyproject.toml for source layout:
toml
[tool.setuptools.packages.find]
where = ["src"]my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ ├── utils.py
│ └── py.typed # 用于类型提示
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_utils.py
└── docs/
└── index.md优势:
- 避免意外导入源码目录
- 更清晰的测试导入路径
- 更好的隔离性
源码布局对应的pyproject.toml配置:
toml
[tool.setuptools.packages.find]
where = ["src"]Pattern 2: Flat Layout
模式2:扁平布局
my-package/
├── pyproject.toml
├── README.md
├── my_package/
│ ├── __init__.py
│ └── module.py
└── tests/
└── test_module.pySimpler but:
- Can import package without installing
- Less professional for libraries
my-package/
├── pyproject.toml
├── README.md
├── my_package/
│ ├── __init__.py
│ └── module.py
└── tests/
└── test_module.py更简单但存在以下问题:
- 无需安装即可导入包
- 对于库来说不够专业
Pattern 3: Multi-Package Project
模式3:多包项目
project/
├── pyproject.toml
├── packages/
│ ├── package-a/
│ │ └── src/
│ │ └── package_a/
│ └── package-b/
│ └── src/
│ └── package_b/
└── tests/project/
├── pyproject.toml
├── packages/
│ ├── package-a/
│ │ └── src/
│ │ └── package_a/
│ └── package-b/
│ └── src/
│ └── package_b/
└── tests/Complete pyproject.toml Examples
完整pyproject.toml示例
Pattern 4: Full-Featured pyproject.toml
模式4:全功能pyproject.toml
toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-awesome-package"
version = "1.0.0"
description = "An awesome Python package"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "you@example.com"},
]
maintainers = [
{name = "Maintainer Name", email = "maintainer@example.com"},
]
keywords = ["example", "package", "awesome"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"requests>=2.28.0,<3.0.0",
"click>=8.0.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
]
docs = [
"sphinx>=5.0.0",
"sphinx-rtd-theme>=1.0.0",
]
all = [
"my-awesome-package[dev,docs]",
]
[project.urls]
Homepage = "https://github.com/username/my-awesome-package"
Documentation = "https://my-awesome-package.readthedocs.io"
Repository = "https://github.com/username/my-awesome-package"
"Bug Tracker" = "https://github.com/username/my-awesome-package/issues"
Changelog = "https://github.com/username/my-awesome-package/blob/main/CHANGELOG.md"
[project.scripts]
my-cli = "my_package.cli:main"
awesome-tool = "my_package.tools:run"
[project.entry-points."my_package.plugins"]
plugin1 = "my_package.plugins:plugin1"
[tool.setuptools]
package-dir = {"" = "src"}
zip-safe = false
[tool.setuptools.packages.find]
where = ["src"]
include = ["my_package*"]
exclude = ["tests*"]
[tool.setuptools.package-data]
my_package = ["py.typed", "*.pyi", "data/*.json"]toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-awesome-package"
version = "1.0.0"
description = "An awesome Python package"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "you@example.com"},
]
maintainers = [
{name = "Maintainer Name", email = "maintainer@example.com"},
]
keywords = ["example", "package", "awesome"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"requests>=2.28.0,<3.0.0",
"click>=8.0.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
]
docs = [
"sphinx>=5.0.0",
"sphinx-rtd-theme>=1.0.0",
]
all = [
"my-awesome-package[dev,docs]",
]
[project.urls]
Homepage = "https://github.com/username/my-awesome-package"
Documentation = "https://my-awesome-package.readthedocs.io"
Repository = "https://github.com/username/my-awesome-package"
"Bug Tracker" = "https://github.com/username/my-awesome-package/issues"
Changelog = "https://github.com/username/my-awesome-package/blob/main/CHANGELOG.md"
[project.scripts]
my-cli = "my_package.cli:main"
awesome-tool = "my_package.tools:run"
[project.entry-points."my_package.plugins"]
plugin1 = "my_package.plugins:plugin1"
[tool.setuptools]
package-dir = {"" = "src"}
zip-safe = false
[tool.setuptools.packages.find]
where = ["src"]
include = ["my_package*"]
exclude = ["tests*"]
[tool.setuptools.package-data]
my_package = ["py.typed", "*.pyi", "data/*.json"]Black configuration
Black配置
[tool.black]
line-length = 100
target-version = ["py38", "py39", "py310", "py311"]
include = '.pyi?$'
[tool.black]
line-length = 100
target-version = ["py38", "py39", "py310", "py311"]
include = '.pyi?$'
Ruff configuration
Ruff配置
[tool.ruff]
line-length = 100
target-version = "py38"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.ruff]
line-length = 100
target-version = "py38"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
MyPy configuration
MyPy配置
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Pytest configuration
Pytest配置
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "-v --cov=my_package --cov-report=term-missing"
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "-v --cov=my_package --cov-report=term-missing"
Coverage configuration
Coverage配置
[tool.coverage.run]
source = ["src"]
omit = ["/tests/"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def repr",
"raise AssertionError",
"raise NotImplementedError",
]
undefined[tool.coverage.run]
source = ["src"]
omit = ["/tests/"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def repr",
"raise AssertionError",
"raise NotImplementedError",
]
undefinedPattern 5: Dynamic Versioning
模式5:动态版本管理
toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
description = "Package with dynamic version"
[tool.setuptools.dynamic]
version = {attr = "my_package.__version__"}toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
description = "Package with dynamic version"
[tool.setuptools.dynamic]
version = {attr = "my_package.__version__"}Or use setuptools-scm for git-based versioning
或使用setuptools-scm基于Git进行版本管理
[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
**In **init**.py:**
```python[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
**在__init__.py中配置:**
```pythonsrc/my_package/init.py
src/my_package/init.py
version = "1.0.0"
version = "1.0.0"
Or with setuptools-scm
或使用setuptools-scm
from importlib.metadata import version
version = version("my-package")
undefinedfrom importlib.metadata import version
version = version("my-package")
undefinedCommand-Line Interface (CLI) Patterns
命令行界面(CLI)模式
Pattern 6: CLI with Click
模式6:基于Click的CLI
python
undefinedpython
undefinedsrc/my_package/cli.py
src/my_package/cli.py
import click
@click.group()
@click.version_option()
def cli():
"""My awesome CLI tool."""
pass
@cli.command()
@click.argument("name")
@click.option("--greeting", default="Hello", help="Greeting to use")
def greet(name: str, greeting: str):
"""Greet someone."""
click.echo(f"{greeting}, {name}!")
@cli.command()
@click.option("--count", default=1, help="Number of times to repeat")
def repeat(count: int):
"""Repeat a message."""
for i in range(count):
click.echo(f"Message {i + 1}")
def main():
"""Entry point for CLI."""
cli()
if name == "main":
main()
**Register in pyproject.toml:**
```toml
[project.scripts]
my-tool = "my_package.cli:main"Usage:
bash
pip install -e .
my-tool greet World
my-tool greet Alice --greeting="Hi"
my-tool repeat --count=3import click
@click.group()
@click.version_option()
def cli():
"""My awesome CLI tool."""
pass
@cli.command()
@click.argument("name")
@click.option("--greeting", default="Hello", help="Greeting to use")
def greet(name: str, greeting: str):
"""Greet someone."""
click.echo(f"{greeting}, {name}!")
@cli.command()
@click.option("--count", default=1, help="Number of times to repeat")
def repeat(count: int):
"""Repeat a message."""
for i in range(count):
click.echo(f"Message {i + 1}")
def main():
"""CLI入口点。"""
cli()
if name == "main":
main()
**在pyproject.toml中注册:**
```toml
[project.scripts]
my-tool = "my_package.cli:main"使用方式:
bash
pip install -e .
my-tool greet World
my-tool greet Alice --greeting="Hi"
my-tool repeat --count=3Pattern 7: CLI with argparse
模式7:基于argparse的CLI
python
undefinedpython
undefinedsrc/my_package/cli.py
src/my_package/cli.py
import argparse
import sys
def main():
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="My awesome tool",
prog="my-tool"
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s 1.0.0"
)
subparsers = parser.add_subparsers(dest="command", help="Commands")
# Add subcommand
process_parser = subparsers.add_parser("process", help="Process data")
process_parser.add_argument("input_file", help="Input file path")
process_parser.add_argument(
"--output", "-o",
default="output.txt",
help="Output file path"
)
args = parser.parse_args()
if args.command == "process":
process_data(args.input_file, args.output)
else:
parser.print_help()
sys.exit(1)def process_data(input_file: str, output_file: str):
"""Process data from input to output."""
print(f"Processing {input_file} -> {output_file}")
if name == "main":
main()
undefinedimport argparse
import sys
def main():
"""CLI主入口点。"""
parser = argparse.ArgumentParser(
description="My awesome tool",
prog="my-tool"
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s 1.0.0"
)
subparsers = parser.add_subparsers(dest="command", help="Commands")
# 添加子命令
process_parser = subparsers.add_parser("process", help="处理数据")
process_parser.add_argument("input_file", help="输入文件路径")
process_parser.add_argument(
"--output", "-o",
default="output.txt",
help="输出文件路径"
)
args = parser.parse_args()
if args.command == "process":
process_data(args.input_file, args.output)
else:
parser.print_help()
sys.exit(1)def process_data(input_file: str, output_file: str):
"""将数据从输入文件处理至输出文件。"""
print(f"Processing {input_file} -> {output_file}")
if name == "main":
main()
undefinedBuilding and Publishing
构建与发布
Pattern 8: Build Package Locally
模式8:本地构建包
bash
undefinedbash
undefinedInstall build tools
安装构建工具
pip install build twine
pip install build twine
Build distribution
构建分发包
python -m build
python -m build
This creates:
执行后会生成:
dist/
dist/
my-package-1.0.0.tar.gz (source distribution)
my-package-1.0.0.tar.gz (源码分发包)
my_package-1.0.0-py3-none-any.whl (wheel)
my_package-1.0.0-py3-none-any.whl (wheel包)
Check the distribution
检查分发包
twine check dist/*
undefinedwine check dist/*
undefinedPattern 9: Publishing to PyPI
模式9:发布至PyPI
bash
undefinedbash
undefinedInstall publishing tools
安装发布工具
pip install twine
pip install twine
Test on TestPyPI first
先在TestPyPI测试
twine upload --repository testpypi dist/*
wine upload --repository testpypi dist/*
Install from TestPyPI to test
从TestPyPI安装进行测试
pip install --index-url https://test.pypi.org/simple/ my-package
pip install --index-url https://test.pypi.org/simple/ my-package
If all good, publish to PyPI
测试通过后,发布至PyPI
twine upload dist/*
**Using API tokens (recommended):**
```bashwine upload dist/*
**推荐使用API令牌:**
```bashCreate ~/.pypirc
创建~/.pypirc文件
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = token
password = pypi-...your-token...
[testpypi]
username = token
password = pypi-...your-test-token...
undefined[distutils]
index-servers =
pypi
testpypi
[pypi]
username = token
password = pypi-...your-token...
[testpypi]
username = token
password = pypi-...your-test-token...
undefinedPattern 10: Automated Publishing with GitHub Actions
模式10:使用GitHub Actions自动化发布
yaml
undefinedyaml
undefined.github/workflows/publish.yml
.github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*undefinedname: 发布至PyPI
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 设置Python环境
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: 安装依赖
run: |
pip install build twine
- name: 构建包
run: python -m build
- name: 检查包
run: twine check dist/*
- name: 发布至PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*undefinedAdvanced Patterns
高级模式
Pattern 11: Including Data Files
模式11:包含数据文件
toml
[tool.setuptools.package-data]
my_package = [
"data/*.json",
"templates/*.html",
"static/css/*.css",
"py.typed",
]Accessing data files:
python
undefinedtoml
[tool.setuptools.package-data]
my_package = [
"data/*.json",
"templates/*.html",
"static/css/*.css",
"py.typed",
]访问数据文件:
python
undefinedsrc/my_package/loader.py
src/my_package/loader.py
from importlib.resources import files
import json
def load_config():
"""Load configuration from package data."""
config_file = files("my_package").joinpath("data/config.json")
with config_file.open() as f:
return json.load(f)
from importlib.resources import files
import json
def load_config():
"""从包数据中加载配置。"""
config_file = files("my_package").joinpath("data/config.json")
with config_file.open() as f:
return json.load(f)
Python 3.9+
Python 3.9+
from importlib.resources import files
data = files("my_package").joinpath("data/file.txt").read_text()
undefinedfrom importlib.resources import files
data = files("my_package").joinpath("data/file.txt").read_text()
undefinedPattern 12: Namespace Packages
模式12:命名空间包
For large projects split across multiple repositories:
undefined适用于拆分为多个仓库的大型项目:
undefinedPackage 1: company-core
包1:company-core
company/
└── core/
├── init.py
└── models.py
company/
└── core/
├── init.py
└── models.py
Package 2: company-api
包2:company-api
company/
└── api/
├── init.py
└── routes.py
**Do NOT include **init**.py in the namespace directory (company/):**
```tomlcompany/
└── api/
├── init.py
└── routes.py
**注意:命名空间目录(company/)中不要包含__init__.py文件:**
```tomlcompany-core/pyproject.toml
company-core/pyproject.toml
[project]
name = "company-core"
[tool.setuptools.packages.find]
where = ["."]
include = ["company.core*"]
[project]
name = "company-core"
[tool.setuptools.packages.find]
where = ["."]
include = ["company.core*"]
company-api/pyproject.toml
company-api/pyproject.toml
[project]
name = "company-api"
[tool.setuptools.packages.find]
where = ["."]
include = ["company.api*"]
**Usage:**
```python[project]
name = "company-api"
[tool.setuptools.packages.find]
where = ["."]
include = ["company.api*"]
**使用方式:**
```pythonBoth packages can be imported under same namespace
两个包可在同一命名空间下导入
from company.core import models
from company.api import routes
undefinedfrom company.core import models
from company.api import routes
undefinedPattern 13: C Extensions
模式13:C扩展
toml
[build-system]
requires = ["setuptools>=61.0", "wheel", "Cython>=0.29"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
ext-modules = [
{name = "my_package.fast_module", sources = ["src/fast_module.c"]},
]Or with setup.py:
python
undefinedtoml
[build-system]
requires = ["setuptools>=61.0", "wheel", "Cython>=0.29"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
ext-modules = [
{name = "my_package.fast_module", sources = ["src/fast_module.c"]},
]或使用setup.py配置:
python
undefinedsetup.py
setup.py
from setuptools import setup, Extension
setup(
ext_modules=[
Extension(
"my_package.fast_module",
sources=["src/fast_module.c"],
include_dirs=["src/include"],
)
]
)
undefinedfrom setuptools import setup, Extension
setup(
ext_modules=[
Extension(
"my_package.fast_module",
sources=["src/fast_module.c"],
include_dirs=["src/include"],
)
]
)
undefinedVersion Management
版本管理
Pattern 14: Semantic Versioning
模式14:语义化版本
python
undefinedpython
undefinedsrc/my_package/init.py
src/my_package/init.py
version = "1.2.3"
version = "1.2.3"
Semantic versioning: MAJOR.MINOR.PATCH
语义化版本格式:MAJOR.MINOR.PATCH
MAJOR: Breaking changes
MAJOR:不兼容的API变更
MINOR: New features (backward compatible)
MINOR:新增向后兼容的功能
PATCH: Bug fixes
PATCH:向后兼容的问题修复
**Version constraints in dependencies:**
```toml
dependencies = [
"requests>=2.28.0,<3.0.0", # Compatible range
"click~=8.1.0", # Compatible release (~= 8.1.0 means >=8.1.0,<8.2.0)
"pydantic>=2.0", # Minimum version
"numpy==1.24.3", # Exact version (avoid if possible)
]
**依赖中的版本约束:**
```toml
dependencies = [
"requests>=2.28.0,<3.0.0", # 兼容版本范围
"click~=8.1.0", # 兼容发布版本(~=8.1.0 表示 >=8.1.0,<8.2.0)
"pydantic>=2.0", # 最低版本要求
"numpy==1.24.3", # 精确版本(尽量避免使用)
]Pattern 15: Git-Based Versioning
模式15:基于Git的版本管理
toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
version_scheme = "post-release"
local_scheme = "dirty-tag"Creates versions like:
- (from git tag)
1.0.0 - (3 commits after tag)
1.0.1.dev3+g1234567
toml
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
[tool.setuptools_scm]
write_to = "src/my_package/_version.py"
version_scheme = "post-release"
local_scheme = "dirty-tag"生成的版本示例:
- (来自Git标签)
1.0.0 - (标签之后的第3次提交)
1.0.1.dev3+g1234567
Testing Installation
安装测试
Pattern 16: Editable Install
模式16:可编辑安装
bash
undefinedbash
undefinedInstall in development mode
以开发模式安装
pip install -e .
pip install -e .
With optional dependencies
包含可选依赖
pip install -e ".[dev]"
pip install -e ".[dev,docs]"
pip install -e ".[dev]"
pip install -e ".[dev,docs]"
Now changes to source code are immediately reflected
此时源码修改会立即生效
undefinedundefinedPattern 17: Testing in Isolated Environment
模式17:在隔离环境中测试
bash
undefinedbash
undefinedCreate virtual environment
创建虚拟环境
python -m venv test-env
source test-env/bin/activate # Linux/Mac
python -m venv test-env
source test-env/bin/activate # Linux/Mac系统
test-env\Scripts\activate # Windows
test-env\Scripts\activate # Windows系统
Install package
安装包
pip install dist/my_package-1.0.0-py3-none-any.whl
pip install dist/my_package-1.0.0-py3-none-any.whl
Test it works
测试包是否可用
python -c "import my_package; print(my_package.version)"
python -c "import my_package; print(my_package.version)"
Test CLI
测试CLI工具
my-tool --help
my-tool --help
Cleanup
清理环境
deactivate
rm -rf test-env
undefineddeactivate
rm -rf test-env
undefinedDocumentation
文档
Pattern 18: README.md Template
模式18:README.md模板
markdown
undefinedmarkdown
undefinedMy Package
My Package
Installation
安装
bash
pip install my-packageundefinedbash
pip install my-packageundefinedQuick Start
快速开始
python
from my_package import something
result = something.do_stuff()python
from my_package import something
result = something.do_stuff()Features
功能特性
- Feature 1
- Feature 2
- Feature 3
- 特性1
- 特性2
- 特性3
Documentation
文档
Full documentation: https://my-package.readthedocs.io
Development
开发
bash
git clone https://github.com/username/my-package.git
cd my-package
pip install -e ".[dev]"
pytestbash
git clone https://github.com/username/my-package.git
cd my-package
pip install -e ".[dev]"
pytestLicense
许可证
MIT
undefinedMIT
undefinedCommon Patterns
常见模式
Pattern 19: Multi-Architecture Wheels
模式19:多架构Wheel包
yaml
undefinedyaml
undefined.github/workflows/wheels.yml
.github/workflows/wheels.yml
name: Build wheels
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: pypa/cibuildwheel@v2.16.2
- uses: actions/upload-artifact@v3
with:
path: ./wheelhouse/*.whlundefinedname: 构建Wheel包
on: [push, pull_request]
jobs:
build_wheels:
name: 在${{ matrix.os }}上构建Wheel包
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: 构建Wheel包
uses: pypa/cibuildwheel@v2.16.2
- uses: actions/upload-artifact@v3
with:
path: ./wheelhouse/*.whlundefinedPattern 20: Private Package Index
模式20:私有包索引
bash
undefinedbash
undefinedInstall from private index
从私有索引安装包
pip install my-package --index-url https://private.pypi.org/simple/
pip install my-package --index-url https://private.pypi.org/simple/
Or add to pip.conf
或添加至pip.conf文件
[global]
index-url = https://private.pypi.org/simple/
extra-index-url = https://pypi.org/simple/
[global]
index-url = https://private.pypi.org/simple/
extra-index-url = https://pypi.org/simple/
Upload to private index
上传至私有索引
twine upload --repository-url https://private.pypi.org/ dist/*
undefinedwine upload --repository-url https://private.pypi.org/ dist/*
undefinedFile Templates
文件模板
.gitignore for Python Packages
Python包的.gitignore模板
gitignore
undefinedgitignore
undefinedBuild artifacts
构建产物
build/
dist/
*.egg-info/
*.egg
.eggs/
build/
dist/
*.egg-info/
*.egg
.eggs/
Python
Python文件
pycache/
*.py[cod]
*$py.class
*.so
pycache/
*.py[cod]
*$py.class
*.so
Virtual environments
虚拟环境
venv/
env/
ENV/
venv/
env/
ENV/
IDE
IDE配置
.vscode/
.idea/
*.swp
.vscode/
.idea/
*.swp
Testing
测试相关
.pytest_cache/
.coverage
htmlcov/
.pytest_cache/
.coverage
htmlcov/
Distribution
分发包
*.whl
*.tar.gz
undefined*.whl
*.tar.gz
undefinedMANIFEST.in
MANIFEST.in模板
undefinedundefinedMANIFEST.in
MANIFEST.in
include README.md
include LICENSE
include pyproject.toml
recursive-include src/my_package/data *.json
recursive-include src/my_package/templates *.html
recursive-exclude * pycache
recursive-exclude * *.py[co]
undefinedinclude README.md
include LICENSE
include pyproject.toml
recursive-include src/my_package/data *.json
recursive-include src/my_package/templates *.html
recursive-exclude * pycache
recursive-exclude * *.py[co]
undefinedChecklist for Publishing
发布检查清单
- Code is tested (pytest passing)
- Documentation is complete (README, docstrings)
- Version number updated
- CHANGELOG.md updated
- License file included
- pyproject.toml is complete
- Package builds without errors
- Installation tested in clean environment
- CLI tools work (if applicable)
- PyPI metadata is correct (classifiers, keywords)
- GitHub repository linked
- Tested on TestPyPI first
- Git tag created for release
- 代码已测试(pytest用例全部通过)
- 文档完整(README、文档字符串)
- 版本号已更新
- CHANGELOG.md已更新
- 包含LICENSE文件
- pyproject.toml配置完整
- 包可正常构建
- 已在干净环境中测试安装
- CLI工具可正常使用(若有)
- PyPI元数据正确(分类器、关键词)
- 已关联GitHub仓库
- 已在TestPyPI测试
- 已为版本创建Git标签
Resources
参考资源
- Python Packaging Guide: https://packaging.python.org/
- PyPI: https://pypi.org/
- TestPyPI: https://test.pypi.org/
- setuptools documentation: https://setuptools.pypa.io/
- build: https://pypa-build.readthedocs.io/
- twine: https://twine.readthedocs.io/
- Python打包指南:https://packaging.python.org/
- PyPI:https://pypi.org/
- TestPyPI:https://test.pypi.org/
- setuptools文档:https://setuptools.pypa.io/
- build工具文档:https://pypa-build.readthedocs.io/
- twine工具文档:https://twine.readthedocs.io/
Best Practices Summary
最佳实践总结
- Use src/ layout for cleaner package structure
- Use pyproject.toml for modern packaging
- Pin build dependencies in build-system.requires
- Version appropriately with semantic versioning
- Include all metadata (classifiers, URLs, etc.)
- Test installation in clean environments
- Use TestPyPI before publishing to PyPI
- Document thoroughly with README and docstrings
- Include LICENSE file
- Automate publishing with CI/CD
- 使用src/布局:更清晰的包结构
- 使用pyproject.toml:遵循现代打包标准
- 固定构建依赖:在build-system.requires中指定
- 合理版本管理:使用语义化版本
- 完善元数据:包含分类器、URL等信息
- 在干净环境测试安装:避免依赖问题
- 先在TestPyPI测试:再发布至正式环境
- 充分文档化:编写README和文档字符串
- 包含LICENSE文件:明确许可证
- 自动化发布:使用CI/CD流程