Python 库开发零基础完整教程(项目→打包→发布全流程)从项目结构设计到 PyPI 发布,一站式掌握 Python 库开发
什么是 Python 库、项目结构、pyproject.toml 配置、src 布局、编写代码、打包分发、PyPI 发布、版本管理、测试与文档全覆盖
目录
一、什么是 Python 库,解决什么核心问题
Python 库(Library / Package)是一组可复用代码的集合,封装为统一接口供其他项目通过 pip install 安装使用。
❌ 脚本方式(难以复用)
# 复制粘贴 utils.py 到每个项目 # 版本混乱,无统一管理 # 手动处理依赖 # 无法 pip install
代码散落在各项目中,更新需逐一修改,缺乏版本控制和依赖管理。
✅ 标准化库开发
# pip install mylib from mylib import helper helper.do_something() # 版本管理,依赖声明 # 自动处理依赖树
代码集中维护,pip 一键安装,版本语义化,依赖自动解析。
Python 库四大核心价值
代码复用 一次编写,pip install 随处使用,无需复制粘贴
版本管理 语义化版本号,用户锁定特定版本,避免破坏性变更
依赖声明 在 pyproject.toml 中声明依赖,pip 自动解析安装
生态分发 发布到 PyPI 后全球开发者可通过 pip 直接安装
二、项目结构设计(src layout 标准)
2.1 推荐项目目录结构
现代 Python 库推荐使用 src layout,将库代码放在 src/ 目录下,避免打包时意外导入本地开发环境中的同名包。
mylib/ # 项目根目录 ├── src/ # 源码目录(src layout) │ └── mylib/ # 库包名,与项目名一致或相关 │ ├── __init__.py # 包入口,暴露对外接口 │ ├── core.py # 核心功能模块 │ ├── utils.py # 工具函数 │ └── subpkg/ # 子包(可选) │ ├── __init__.py │ └── helpers.py ├── tests/ # 测试目录 │ ├── __init__.py │ ├── test_core.py │ └── test_utils.py ├── docs/ # 文档目录(可选) │ ├── guide.md │ └── api.md ├── pyproject.toml # 项目元数据与构建配置【核心】 ├── README.md # 项目说明 ├── LICENSE # 开源许可证 ├── .gitignore # Git 忽略规则 └── CHANGELOG.md # 版本变更日志
❌ 旧版 flat layout
mylib/ ├── mylib/ # 代码与项目根混在一起 │ ├── __init__.py │ └── core.py ├── setup.py # 旧式配置 └── setup.cfg
打包时可能意外导入本地已安装的版本,导致难以排查的 Bug。
✅ 推荐 src layout
mylib/ ├── src/ │ └── mylib/ # 代码在 src/ 下 │ ├── __init__.py │ └── core.py ├── pyproject.toml # 现代配置 └── tests/
强制从打包后的安装版本导入,避免本地环境干扰,构建更可靠。
2.2 .gitignore 标准配置
# Python __pycache__/ *.py[cod] *.egg-info/ dist/ build/ *.egg # Virtual Env .venv/ venv/ .env # IDE .vscode/ .idea/ # OS .DS_Store Thumbs.db
三、pyproject.toml 完整配置
3.1 完整 pyproject.toml 示例
[build-system]
requires = ["setuptools>=75.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "mylib"
version = "0.1.0"
description = "A short description of your library"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
keywords = ["example", "template"]
authors = [
{name = "Your Name", email = "your@email.com"},
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"ruff>=0.5",
"mypy>=1.10",
"pre-commit>=3.0",
]
docs = [
"mkdocs>=1.6",
"mkdocs-material>=9.0",
]
[project.urls]
Homepage = "https://github.com/yourname/mylib"
Documentation = "https://mylib.readthedocs.io"
Repository = "https://github.com/yourname/mylib"
"Bug Tracker" = "https://github.com/yourname/mylib/issues"
[tool.setuptools.packages.find]
where = ["src"] # src layout 关键配置
include = ["mylib*"] # 包含的包名模式
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=mylib"
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.mypy]
python_version = "3.10"
strict = true
3.2 字段说明速查
| 字段 | 作用 | 是否必填 |
|---|---|---|
| [build-system] | 声明构建工具(setuptools / hatchling / flit) | 是 |
| [project] name | PyPI 上的包名,不能与已有包冲突 | 是 |
| [project] version | 当前版本号,遵循语义化版本 | 是 |
| [project] dependencies | 运行时依赖列表 | 推荐 |
| [project] requires-python | 最低 Python 版本约束 | 推荐 |
| [project.optional-dependencies] | 可选依赖组(dev/docs/test) | 可选 |
| [tool.setuptools.packages.find] | src layout 时必须配置 where | src layout 必填 |
四、编写库代码与 __init__.py
4.1 __init__.py 的职责
__init__.py 是包的入口文件,控制对外暴露的接口。好的库只暴露用户需要使用的 API,隐藏内部实现细节。
# src/mylib/__init__.py
"""mylib - A short description of your library."""
from .core import MyClass, my_function
from .utils import helper
__all__ = [
"MyClass",
"my_function",
"helper",
]
__version__ = "0.1.0"
4.2 核心模块编写示例
# src/mylib/core.py
"""Core module - implements the main functionality."""
from typing import Optional
class MyClass:
"""Main class of the library."""
def __init__(self, name: str, value: int = 0):
self.name = name
self._value = value
@property
def value(self) -> int:
return self._value
@value.setter
def value(self, val: int) -> None:
if val < 0:
raise ValueError("Value must be non-negative")
self._value = val
def greet(self) -> str:
return f"Hello from {self.name}!"
def my_function(param: str, *, flag: bool = False) -> dict:
"""Main processing function.
Args:
param: Input parameter.
flag: Whether to enable extra processing.
Returns:
A dictionary with processed results.
"""
result = {"input": param, "processed": param.upper()}
if flag:
result["extra"] = "enabled"
return result
4.3 工具模块
# src/mylib/utils.py
"""Utility functions."""
import os
from pathlib import Path
from typing import Union
def ensure_dir(path: Union[str, Path]) -> Path:
"""Ensure a directory exists, creating it if needed.
Args:
path: Directory path to ensure.
Returns:
The Path object of the directory.
"""
p = Path(path)
p.mkdir(parents=True, exist_ok=True)
return p
def read_file(path: Union[str, Path]) -> str:
"""Read a text file content.
Args:
path: Path to the file.
Returns:
File content as string.
"""
with open(path, "r", encoding="utf-8") as f:
return f.read()
五、依赖管理与版本控制
5.1 依赖声明原则
依赖声明三条规则
1. 最小版本约束 只声明下界(如 requests>=2.31),不锁死上限,避免与用户其他依赖冲突
2. 按功能分组 运行时依赖放 dependencies,开发工具放 [dev],文档工具放 [docs]
3. 平台特定依赖 使用 environment markers 实现条件依赖
# 条件依赖示例 [project.dependencies] requests>=2.31 # 仅 Windows 需要 colorama>=0.4 ; sys_platform == "win32" [project.optional-dependencies] dev = ["pytest>=8", "ruff>=0.5", "mypy>=1.10"] docs = ["mkdocs>=1.6", "mkdocs-material>=9"]
5.2 语义化版本(SemVer)
1.0.0 → 首次稳定发布 1.2.0 → 新增功能,向后兼容 1.2.1 → Bug 修复 2.0.0 → 破坏性 API 变更 # 预发布标签 1.0.0a1 → Alpha 内部测试 1.0.0b1 → Beta 公开测试 1.0.0rc1 → Release Candidate 候选版
5.3 CHANGELOG.md 模板
# Changelog ## [0.2.0] - 2026-07-15 ### Added - 新增 `MyClass.greet()` 方法 - 支持 Python 3.13 ### Changed - 重构 utils 模块,废弃 `old_helper()` - 提升核心算法性能约 30% ### Fixed - 修复 Windows 下路径分隔符问题 ## [0.1.0] - 2026-06-01 ### Added - 初始版本发布 - 核心类 `MyClass` 和工具函数
六、构建与打包(uv / setuptools)
6.1 构建工具对比
| 工具 | 特点 | 适用场景 |
|---|---|---|
| setuptools | Python 生态最成熟,兼容性最好 | 通用项目,推荐新手 |
| hatchling | 现代简洁,Hatch 生态 | 追求简洁配置的新项目 |
| flit_core | 极简,纯 Python 包 | 纯 Python 无 C 扩展的项目 |
| pdm-backend | PDM 生态,PEP 621 原生 | 使用 PDM 管理的项目 |
| uv | 极速,Rust 编写,现代工具链 | 追求速度的新项目(推荐) |
6.2 使用 uv 构建
# 安装 uv(推荐现代方式) pip install uv # 构建 wheel 和 sdist uv build Successfully built dist/mylib-0.1.0-py3-none-any.whl Successfully built dist/mylib-0.1.0.tar.gz # 查看构建产物 ls dist/ mylib-0.1.0-py3-none-any.whl mylib-0.1.0.tar.gz
6.3 使用 setuptools 构建
# 安装构建工具 pip install build # 构建 python -m build * Creating venv isolated environment... * Installing packages in isolated environment... * Building wheel... Successfully built mylib-0.1.0-py3-none-any.whl * Building sdist... Successfully built mylib-0.1.0.tar.gz
6.4 构建产物说明
Wheel (.whl)
预构建的二进制分发格式,安装时无需执行构建步骤,即装即用。
mylib-0.1.0-py3-none-any.whl
├── mylib/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
└── mylib-0.1.0.dist-info/
├── METADATA
├── WHEEL
└── RECORD
Source Distribution (.tar.gz)
源码分发格式,包含源码和 pyproject.toml,安装时在目标机器上构建。
mylib-0.1.0.tar.gz ├── src/ │ └── mylib/ ├── pyproject.toml ├── README.md ├── LICENSE └── PKG-INFO
七、发布到 PyPI 全流程
7.1 发布流程概览
- 注册 PyPI 账号:pypi.org/account/register 推荐启用双因素认证(2FA)
- 创建 API Token:Account Settings → API tokens → Add API token
Token 格式:
pypi-xxxxxxxx,保存到本地安全位置 - 构建 wheel 和 sdist:
uv build或python -m build产物生成在 dist/ 目录下 - 上传到 PyPI:
uv publish # 方式一:uv 一键发布
twine upload dist/* # 方式二:传统 twine 上传
首次发布需输入用户名
__token__和密码(Token 值) - 验证发布:
pip install mylibPyPI 索引更新后即可安装,通常 1-2 分钟
7.2 先发布到 TestPyPI 测试
# 配置 TestPyPI 仓库 uv publish --publish-url https://test.pypi.org/legacy/ # 从 TestPyPI 安装测试 pip install --index-url https://test.pypi.org/simple/ mylib
7.3 发布后验证
# 从 PyPI 安装 pip install mylib # 检查版本 pip show mylib Name: mylib Version: 0.1.0 Summary: A short description of your library Home-page: https://github.com/yourname/mylib # 查看依赖 pip show mylib | Select-String "Requires" Requires: requests, pydantic
八、测试与 CI 集成
8.1 pytest 测试示例
# tests/test_core.py
import pytest
from mylib import MyClass, my_function
class TestMyClass:
def test_init(self):
obj = MyClass("test", 42)
assert obj.name == "test"
assert obj.value == 42
def test_greet(self):
obj = MyClass("world")
assert obj.greet() == "Hello from world!"
def test_negative_value(self):
obj = MyClass("test")
with pytest.raises(ValueError):
obj.value = -1
class TestMyFunction:
def test_basic(self):
result = my_function("hello")
assert result["input"] == "hello"
assert result["processed"] == "HELLO"
def test_with_flag(self):
result = my_function("hello", flag=True)
assert result["extra"] == "enabled"
@pytest.mark.parametrize("input_val,expected", [
("abc", "ABC"),
("123", "123"),
("", ""),
])
def test_parametrized(self, input_val, expected):
result = my_function(input_val)
assert result["processed"] == expected
8.2 运行测试
# 安装测试依赖 pip install -e ".[dev]" # 运行所有测试 pytest ============================= test session starts ============================= collected 6 items tests/test_core.py ...... [100%] ============================== 6 passed in 0.32s ============================== # 带覆盖率报告 pytest --cov=mylib --cov-report=term-missing
8.3 GitHub Actions CI 配置
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install uv
- run: uv sync --group dev
- run: uv run pytest --cov=mylib
- run: uv run ruff check
- run: uv run mypy src
九、文档编写与自动化
9.1 README.md 规范
# mylib
[](https://pypi.org/project/mylib/)
[](https://pypi.org/project/mylib/)
[](https://github.com/yourname/mylib/actions)
A short description of your library.
## Installation
```bash
pip install mylib
```
## Quick Start
```python
from mylib import MyClass
obj = MyClass("world")
print(obj.greet())
# Output: Hello from world!
```
## Documentation
Full documentation: https://mylib.readthedocs.io
## License
This project is licensed under the MIT License.
9.2 MkDocs 文档站点
# mkdocs.yml
site_name: mylib
site_description: A short description of your library
theme:
name: material
features:
- navigation.tabs
- navigation.sections
nav:
- Home: index.md
- Guide:
- Getting Started: guide/getting-started.md
- Usage: guide/usage.md
- API Reference: api/
- Changelog: changelog.md
plugins:
- mkdocstrings:
handlers:
python:
paths: [src]
# 安装文档依赖 pip install -e ".[docs]" # 本地预览文档 mkdocs serve INFO - Building documentation... INFO - Serving on http://127.0.0.1:8000 # 构建静态文档 mkdocs build INFO - Documentation built in site/
十、最佳实践与常见问题
10.1 开发完整工作流
10.2 最佳实践清单
开发规范
✅ 使用 src layout 组织项目结构,避免导入歧义
✅ 在 __init__.py 中显式定义 __all__,控制暴露接口
✅ 使用有意义的类型注解(Type Hints),提升代码可读性
✅ 所有公有函数编写 docstring,遵循 Google/NumPy 风格
✅ 使用 ruff 规范代码风格,mypy 做静态类型检查
✅ 配置 pre-commit 自动在提交前检查代码
✅ 遵循语义化版本,发布前更新 CHANGELOG
✅ 使用 GitHub Actions 做 CI 自动测试和多版本验证
10.3 常见问题与解决方案
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| pip install 后找不到模块 | src layout 未配置 packages.find.where | pyproject.toml 中添加 [tool.setuptools.packages.find] where = [“src”] |
| 构建出的包不包含子包 | packages.find.include 模式不匹配 | 设置 include = [“mylib*”] 或已弃用的 namespace_packages |
| PyPI 发布失败:名称已存在 | 包名被占用 | 修改 project.name 为未使用的名称,先搜索 PyPI 确认 |
| twine 上传认证失败 | Token 过期或权限不足 | 重新生成 API Token,确认使用 __token__ 作为用户名 |
| 本地测试通过但 CI 失败 | 本地环境与 CI 不一致 | 使用 requirements.txt 或 uv.lock 锁定版本,确保 CI 与本地一致 |
| 版本号冲突 | 手动修改版本号后忘记更新 | 使用 bumpversion 或 hatch version 等工具自动管理版本号 |
| 依赖地狱 | 依赖版本约束过于严格 | 只设下界不设上界,使用语义化版本范围兼容 |
10.4 完整项目模板
mylib/ ├── .github/workflows/ci.yml ├── src/ │ └── mylib/ │ ├── __init__.py │ ├── core.py │ └── utils.py ├── tests/ │ ├── __init__.py │ ├── test_core.py │ └── test_utils.py ├── docs/ │ ├── index.md │ └── guide/ ├── pyproject.toml ├── README.md ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── .pre-commit-config.yaml └── mkdocs.yml
Python Library Development Complete GuideProject Structure → Build → PyPI Release — Full Workflow
What is a Python library, project structure, pyproject.toml, src layout, coding, packaging, publishing to PyPI, versioning, testing & documentation
Table of Contents
1 What is a Python Library
A Python library (package) is a reusable collection of code, packaged with a standardized interface so other projects can install it via pip install.
Four Core Values
Reusability Write once, pip install anywhere — no copy-paste
Versioning Semantic versioning, users pin exact versions, no breaking surprises
Declared Dependencies List dependencies in pyproject.toml, pip auto-resolves
Ecosystem Distribution Publish to PyPI, reachable by every Python developer globally
2 Project Structure & src Layout
Modern Python libraries use src layout — placing source code under src/ to avoid accidental imports from the development environment.
mylib/ ├── src/ │ └── mylib/ │ ├── __init__.py │ ├── core.py │ ├── utils.py │ └── subpkg/ │ ├── __init__.py │ └── helpers.py ├── tests/ │ ├── test_core.py │ └── test_utils.py ├── docs/ ├── pyproject.toml ├── README.md ├── LICENSE ├── .gitignore └── CHANGELOG.md
3 pyproject.toml Configuration
pyproject.toml is the modern standard configuration file (PEP 621), replacing the legacy setup.py.
[build-system]
requires = ["setuptools>=75.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "mylib"
version = "0.1.0"
description = "A short description of your library"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
dependencies = ["requests>=2.31"]
[tool.setuptools.packages.find]
where = ["src"] # Critical for src layout
include = ["mylib*"]
4 Writing Library Code & __init__.py
The __init__.py controls the public API surface. Expose only what users need.
# src/mylib/__init__.py
from .core import MyClass, my_function
__all__ = ["MyClass", "my_function"]
__version__ = "0.1.0"
# src/mylib/core.py
class MyClass:
def __init__(self, name: str, value: int = 0):
self.name = name
self._value = value
def greet(self) -> str:
return f"Hello from {self.name}!"
5 Dependency & Version Management
Semantic Versioning
Dependency Rules
- Specify only lower bounds:
requests>=2.31 - Group optional deps: dev, docs, test
- Use environment markers for platform-specific deps
6 Building & Packaging
# Build with uv (recommended) pip install uv uv build Successfully built dist/mylib-0.1.0-py3-none-any.whl Successfully built dist/mylib-0.1.0.tar.gz # Or build with setuptools pip install build python -m build
7 Publishing to PyPI
- Register at pypi.org (enable 2FA)
- Create API Token: Account Settings → API tokens → Add API token
- Build:
uv buildorpython -m build - Upload:
uv publishortwine upload dist/* - Verify:
pip install mylib
uv publish --publish-url https://test.pypi.org/legacy/
8 Testing & CI Integration
# tests/test_core.py
import pytest
from mylib import MyClass
def test_greet():
obj = MyClass("world")
assert obj.greet() == "Hello from world!"
pip install -e ".[dev]" pytest --cov=mylib ============================== 6 passed in 0.32s ==============================
GitHub Actions CI
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: {python-version: "${{ matrix.python-version }}"}
- run: pip install uv && uv sync --group dev
- run: uv run pytest --cov=mylib
- run: uv run ruff check && uv run mypy src
9 Documentation
README.md with badges
# mylib [](https://pypi.org/project/mylib/) [](https://pypi.org/project/mylib/) A short description of your library. ## Installation ```bash pip install mylib ```
MkDocs site
pip install -e ".[docs]" mkdocs serve Serving on http://127.0.0.1:8000
10 Best Practices & FAQ
Best Practices Checklist
✅ Use src layout to avoid import ambiguity
✅ Define __all__ in __init__.py to control the public API
✅ Add type hints and docstrings to all public functions
✅ Use ruff for linting, mypy for type checking
✅ Follow semantic versioning, update CHANGELOG per release
✅ Set up GitHub Actions CI for multi-version testing
| Issue | Cause | Solution |
|---|---|---|
| Module not found after install | Missing packages.find in src layout | Add [tool.setuptools.packages.find] where = [“src”] |
| PyPI name conflict | Package name taken | Search PyPI first, choose a unique name |
| Twine auth failure | Token expired | Regenerate API token, use __token__ as username |
| CI fails but local passes | Environment mismatch | Lock dependencies with uv.lock or requirements.txt |
| Dependency hell | Overly strict version constraints | Only set lower bounds, use semver ranges |
