Python 文档编写与注释规范
目录
学习目标:掌握 Python 文档字符串(docstring)规范,学会编写清晰的注释,理解项目文档的组成和工具链。
1. 注释 vs 文档字符串
# 注释(Comment):解释代码"怎么做"
# 使用 # 号,面向开发者
total = sum(items) # 计算总价
# 文档字符串(Docstring):解释"是什么"和"怎么用"
# 使用三引号,面向使用者
def calculate_total(items):
"""计算商品列表的总价。
Args:
items: 商品列表,每个商品包含 price 字段
Returns:
所有商品价格之和
"""
return sum(item['price'] for item in items)2. 文档字符串规范
2.1 Google 风格(推荐)
def fetch_user(user_id: int, include_profile: bool = False) -> dict:
"""获取用户信息。
根据用户 ID 从数据库获取用户信息,可选择是否包含用户档案。
Args:
user_id: 用户唯一标识符,必须为正整数。
include_profile: 是否包含用户档案信息,默认为 False。
Returns:
包含用户信息的字典,格式如下:
{
"id": int,
"name": str,
"email": str,
"profile": dict # 仅当 include_profile=True
}
Raises:
ValueError: 当 user_id 不为正整数时。
UserNotFoundError: 当用户不存在时。
Examples:
>>> fetch_user(1)
{"id": 1, "name": "张三", "email": "zhangsan@example.com"}
>>> fetch_user(1, include_profile=True)
{"id": 1, "name": "张三", "profile": {"age": 25, "city": "北京"}}
"""
if user_id <= 0:
raise ValueError("user_id 必须为正整数")
user = db.get_user(user_id)
if user is None:
raise UserNotFoundError(f"用户 {user_id} 不存在")
if include_profile:
user['profile'] = db.get_profile(user_id)
return user2.2 类和模块文档
"""用户管理模块。
提供用户注册、登录、信息管理等功能。
示例:
from user_manager import UserManager
manager = UserManager()
manager.register("zhangsan", "password123")
"""
class UserManager:
"""用户管理器。
管理用户的注册、认证和信息操作。
Attributes:
db: 数据库连接实例。
cache: 缓存实例。
Example:
>>> manager = UserManager()
>>> user = manager.login("admin", "password")
>>> print(user.name)
管理员
"""
def __init__(self, db_url: str = None):
"""初始化用户管理器。
Args:
db_url: 数据库连接 URL,默认使用配置文件中的地址。
"""
self.db = Database(db_url)
self.cache = {}
def register(self, username: str, password: str) -> dict:
"""注册新用户。
Args:
username: 用户名,3-20 个字符。
password: 密码,至少 8 个字符。
Returns:
新创建的用户信息。
Raises:
ValueError: 用户名或密码不符合要求。
UsernameExistsError: 用户名已被注册。
"""
pass2.3 NumPy 风格
def linear_regression(X, y, alpha=0.01, iterations=1000):
"""线性回归梯度下降。
使用梯度下降算法拟合线性回归模型。
Parameters
----------
X : array_like
特征矩阵,形状为 (n_samples, n_features)。
y : array_like
目标值,形状为 (n_samples,)。
alpha : float, optional
学习率,默认为 0.01。
iterations : int, optional
迭代次数,默认为 1000。
Returns
-------
theta : ndarray
拟合后的参数向量,形状为 (n_features,)。
cost_history : list
每次迭代的损失值。
Notes
-----
学习率过大可能导致不收敛,建议从 0.001 开始调试。
Examples
--------
>>> import numpy as np
>>> X = np.array([[1, 1], [1, 2], [1, 3]])
>>> y = np.array([2, 4, 6])
>>> theta, _ = linear_regression(X, y, alpha=0.1)
"""
pass3. 注释最佳实践
3.1 好的注释
# 解释"为什么"而不是"做什么"
# 使用二分查找因为数据已排序,O(log n) 比 O(n) 快
def find_item(sorted_list, target):
left, right = 0, len(sorted_list) - 1
while left <= right:
mid = (left + right) // 2
if sorted_list[mid] == target:
return mid
elif sorted_list[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 标注 workaround 和 TODO
def get_user_data(user_id):
# TODO: 迁移到新的用户服务 API(预计 v2.0)
# FIXME: 临时方案,需要处理超时情况
response = requests.get(f"{OLD_API_URL}/users/{user_id}")
return response.json()
# 解释复杂的正则表达式
pattern = r'^(\d{4})-(\d{2})-(\d{2})$' # YYYY-MM-DD 格式日期
# 标注魔法数字的来源
RETRY_INTERVAL = 5 # 根据运维建议,5 秒重试间隔
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB,上传限制3.2 坏的注释(应避免)
# ✗ 注释只是在重复代码
i += 1 # i 加 1
# ✗ 过时的注释(代码已改但注释没更新)
# 返回用户列表
def get_user_count(): # 实际返回的是数量
return len(users)
# ✗ 用注释代替函数名
def do_stuff(x, y):
# 这个函数计算两个数的平均值然后乘以 100
return (x + y) / 2 * 100
# ✓ 应该改为
def calculate_percentage_average(x, y):
return (x + y) / 2 * 100
# ✗ 注释掉的代码(用 Git 管理历史)
# old_result = process_old_way(data)
# if old_result > threshold:
# return old_result
result = process_new_way(data)4. 类型提示即文档
# 类型提示本身就是最好的文档
# 无需额外注释说明参数类型
def process_order(
order_id: int,
items: list[dict[str, int | str]],
discount: float = 0.0,
callback: Callable[[int], None] | None = None
) -> dict[str, int | str]:
"""处理订单。
Args:
order_id: 订单 ID。
items: 商品列表,每项包含 name(str) 和 quantity(int)。
discount: 折扣率,0.0-1.0 之间。
callback: 处理完成后的回调函数。
Returns:
处理结果,包含 total 和 status。
"""
pass5. 项目文档组成
project/
├── README.md ← 项目入口文档
├── CHANGELOG.md ← 变更日志
├── CONTRIBUTING.md ← 贡献指南
├── LICENSE ← 开源协议
├── docs/ ← 文档目录
│ ├── installation.md ← 安装指南
│ ├── quickstart.md ← 快速开始
│ ├── api/ ← API 文档
│ └── examples/ ← 示例代码
├── pyproject.toml ← 项目配置
└── src/
└── mypackage/
├── __init__.py ← 包文档
└── ...5.1 README 模板
# 项目名称
一句话描述项目。
## 功能特性
- 特性 1
- 特性 2
- 特性 3
## 安装
pip install package-name
## 快速开始
```python
from package import Module
m = Module()
result = m.run()文档
完整文档请访问:https://package.readthedocs.io
开发
环境准备
pip install -e “.[dev]”
运行测试
pytest
许可证
MIT License
### 5.2 CHANGELOG 模板
```markdown
# 变更日志
## [1.2.0] - 2024-01-15
### Added
- 新增用户头像上传功能
- 添加批量删除 API
### Changed
- 优化数据库查询性能
- 更新依赖版本
### Fixed
- 修复登录页面在 Safari 上的显示问题
- 修复并发情况下的数据竞争
### Deprecated
- `old_api()` 将在 2.0 版本移除,请使用 `new_api()`
## [1.1.0] - 2023-12-01
...6. 自动文档生成
6.1 pdoc
# 安装
pip install pdoc
# 生成 HTML 文档
pdoc ./src/mypackage
# 指定输出目录
pdoc ./src/mypackage -o ./docs
# 启动本地服务器预览
pdoc ./src/mypackage --http localhost:80806.2 Sphinx
# 安装
pip install sphinx sphinx-autodoc-typehints
# 初始化文档
sphinx-quickstart docs
# 生成 API 文档
sphinx-apidoc -o docs/source ./src/mypackage
# 构建 HTML
cd docs
make html# docs/source/conf.py
import os
import sys
sys.path.insert(0, os.path.abspath('../../src'))
project = 'My Package'
author = 'Your Name'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon', # 支持 Google/NumPy 风格
'sphinx_autodoc_typehints',
]
# 使用 Google 风格
napoleon_google_docstring = True
napoleon_numpy_docstring = False# docs/source/index.rst
.. My Package documentation
Welcome to My Package's documentation!
======================================
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
quickstart
api
API Reference
=============
.. automodule:: mypackage
:members:
:undoc-members:
:show-inheritance:6.3 MkDocs(推荐)
# 安装
pip install mkdocs mkdocs-material mkdocstrings[python]
# 初始化
mkdocs new docs# mkdocs.yml
site_name: My Package
theme:
name: material
language: zh
features:
- navigation.tabs
- search.suggest
plugins:
- search
- mkdocstrings:
handlers:
python:
options:
docstring_style: google
nav:
- 首页: index.md
- 安装: installation.md
- 快速开始: quickstart.md
- API:
- 核心模块: api/core.md
- 工具模块: api/utils.md# 本地预览
mkdocs serve
# 构建静态站点
mkdocs build
# 部署到 GitHub Pages
mkdocs gh-deploy7. 内联文档工具
7.1 doctest
def factorial(n: int) -> int:
"""计算阶乘。
>>> factorial(0)
1
>>> factorial(1)
1
>>> factorial(5)
120
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n 必须为非负整数
"""
if n < 0:
raise ValueError("n 必须为非负整数")
if n <= 1:
return 1
return n * factorial(n - 1)
# 运行 doctest
# python -m doctest -v script.py
# 或在 pytest 中
# pytest --doctest-modules7.2 __init__.py 文档
"""My Package.
一个简洁的 Python 工具包,提供 XXX 功能。
基本用法:
>>> from mypackage import Tool
>>> tool = Tool()
>>> tool.run()
主要组件:
- Tool: 主工具类
- Config: 配置管理
- helpers: 辅助函数
"""
from .tool import Tool
from .config import Config
__version__ = "1.0.0"
__author__ = "Your Name"
__all__ = ["Tool", "Config"]8. 文档检查工具
# pydocstyle: 检查文档字符串规范
pip install pydocstyle
pydocstyle src/
# Ruff 也能检查文档
ruff check --select D src/
# 配置(pyproject.toml)[tool.ruff.lint.pydocstyle]
convention = "google" # 或 "numpy"9. 文档编写原则
好的文档应该:
1. 解释 Why,不只是 What
✗ "此函数返回列表"
✓ "返回排序后的列表,因为前端需要有序展示"
2. 保持与代码同步
- 修改代码时同步更新文档
- 过时文档比没有文档更危险
3. 给出示例
- 一个好例子胜过千言万语
- 示例应该是可运行的
4. 面向读者
- API 文档面向使用者
- 内部注释面向维护者
- README 面向新人
5. 简洁明了
- 避免"显然"的描述
- 删除多余的文字10. 小结
| 文档类型 | 工具/格式 | 用途 |
|---|---|---|
| Docstring | Google/NumPy 风格 | 函数/类文档 |
| 注释 | # |
代码内解释 |
| README | Markdown | 项目入口 |
| API 文档 | pdoc/Sphinx/MkDocs | 自动生成 |
| 变更日志 | CHANGELOG.md | 版本记录 |
| doctest | >>> 示例 |
可测试文档 |
11. 练习题
- 为你之前写的模块编写完整的 Google 风格 docstring。
- 使用 MkDocs Material 为项目生成文档网站。
- 为一个函数编写 doctest,并用 pytest 运行验证。
- 审查你项目中的注释,清理不合理的注释并补充缺失的注释。
第九阶段(实用工具)完。接下来将进入第十阶段:综合实战。