一、开篇:代码是写给人看的
好的代码自己会说话,而docstring(文档字符串)就是帮代码"说话"的工具。一个函数如果没有docstring,调用者就只能去读源码或者猜它的用法。
对比有和没有docstring的区别:
# ❌ 没有docstring——只能猜
def calc(a, b, mode=1):
if mode == 1:
return a + b
elif mode == 2:
return a - b
else:
return a * b
# ✅ 有docstring——一目了然
def calc(a: float, b: float, mode: int = 1) -> float:
"""对两个数执行指定的运算。
args:
a: 第一个操作数
b: 第二个操作数
mode: 运算模式,1=加法,2=减法,其他=乘法
returns:
运算结果
raises:
typeerror: 当操作数不是数字时
"""
if mode == 1:
return a + b
elif mode == 2:
return a - b
else:
return a * b
docstring是函数、类、模块的"使用说明书"。更重要的是,python的help()函数和众多文档生成工具都依赖docstring。写好docstring,是对自己(三个月后的你)和同事最大的善意。
二、docstring的基本规则
2.1 位置和格式
# docstring的基本规则:
# 1. 放在函数/类/模块的第一行(def/class之后的第一条语句)
# 2. 用三引号包裹("""或''')
# 3. 可以使用多行
def greet(name: str) -> str:
"""向指定的人打招呼。"""
return f"你好,{name}!"
# 访问docstring
print(greet.__doc__) # 向指定的人打招呼。
# help()查看——更友好的展示
# help(greet)
# 输出:
# help on function greet in module __main__:
#
# greet(name: str) -> str
# 向指定的人打招呼。
# ⚠️ docstring必须是字符串字面量,不能是变量
# def bad():
# doc = "文档" # 这不是docstring!是普通的字符串赋值
# pass
2.2 单行docstring
# 适用场景:函数功能简单明了
def add(a: int, b: int) -> int:
"""返回a和b的和。"""
return a + b
def is_even(n: int) -> bool:
"""判断一个整数是否为偶数。"""
return n % 2 == 0
# pep 257规范:
# - 三引号在同一行开始和结束
# - 使用句号结尾
# - 描述函数的功能(做什么),而不是实现(怎么做)
# - 使用命令式语气:"返回..."而不是"这个函数返回..."
2.3 多行docstring
# 适用场景:函数逻辑复杂,需要详细说明
def fetch_user_data(
user_id: int,
fields: list[str] | none = none,
include_inactive: bool = false,
timeout: int = 30
) -> dict | none:
"""从数据库获取用户数据。
根据用户id查询用户信息。如果提供了fields参数,
只返回指定的字段。默认不包含已停用的用户。
args:
user_id: 用户唯一标识符
fields: 需要返回的字段列表,none表示返回所有字段
include_inactive: 是否包含已停用的用户
timeout: 查询超时时间(秒)
returns:
包含用户数据的字典,如果用户不存在则返回none。
字典的键取决于fields参数。
raises:
valueerror: 当user_id小于等于0时
timeouterror: 查询超时时
databaseerror: 数据库连接失败时
examples:
>>> fetch_user_data(123)
{'name': '张三', 'email': 'zs@test.com', 'age': 25}
>>> fetch_user_data(123, fields=['name', 'age'])
{'name': '张三', 'age': 25}
>>> fetch_user_data(999)
none
"""
if user_id <= 0:
raise valueerror("user_id必须大于0")
# ... 实际实现 ...
return {"name": "张三", "email": "zs@test.com", "age": 25}
三、三大docstring风格对比
3.1 google风格(推荐!)
def send_notification(
user: str,
message: str,
*,
channel: str = "email",
priority: str = "normal",
attachments: list[str] | none = none,
retry_count: int = 3
) -> bool:
"""向用户发送通知消息。
支持多种通知渠道,消息发送失败时自动重试。
高优先级消息会绕过用户的免打扰设置。
args:
user: 接收通知的用户名或用户id
message: 通知内容,支持纯文本
channel: 通知渠道,可选值:\"email\"、\"sms\"、\"push\"、
\"wechat\"。(默认:\"email\")
priority: 优先级,可选值:\"low\"、\"normal\"、\"high\"、
\"urgent\"。(默认:\"normal\")
attachments: 附件文件路径列表,none表示无附件
(默认:none)
retry_count: 失败重试次数(默认:3)
returns:
true表示发送成功,false表示所有重试均失败。
raises:
valueerror: 当channel或priority值不合法时
filenotfounderror: 当附件文件不存在时
example:
>>> send_notification("张三", "您的订单已发货",
... channel="sms", priority="high")
true
>>> send_notification("李四", "服务器告警",
... channel="push", priority="urgent")
true
"""
valid_channels = {"email", "sms", "push", "wechat"}
if channel not in valid_channels:
raise valueerror(f"无效的通知渠道: {channel}")
# ... 实际实现 ...
return true
3.2 numpy风格
def calculate_statistics(
data: list[float],
*,
skip_na: bool = true,
percentiles: list[int] | none = none
) -> dict:
"""计算数据集的描述性统计。
parameters
----------
data : list of float
待分析的数据列表。
skip_na : bool, optional
是否跳过nan值。默认值为true。
percentiles : list of int or none, optional
需要计算的百分位数列表。默认值为none,
不计算百分位数。
returns
-------
dict
包含统计结果的字典,包含以下键:
- "count": 样本数量
- "mean": 平均值
- "std": 标准差
- "min": 最小值
- "max": 最大值
- "percentiles": 百分位数结果(如果指定了percentiles)
raises
------
valueerror
当data为空列表时。
examples
--------
>>> calculate_statistics([1.0, 2.0, 3.0, 4.0, 5.0])
{'count': 5, 'mean': 3.0, 'std': 1.58, 'min': 1.0, 'max': 5.0}
"""
pass
3.3 sphinx/restructuredtext风格
def validate_email(email: str) -> bool:
"""验证邮箱地址格式是否合法。
:param email: 待验证的邮箱地址字符串
:type email: str
:return: 邮箱格式合法返回true,否则返回false
:rtype: bool
:raises typeerror: 当email不是字符串时
验证规则:
1. 包含恰好一个 @ 符号
2. @ 前后都有字符
3. @ 后面部分包含一个 .
::
>>> validate_email("user@example.com")
true
>>> validate_email("invalid-email")
false
"""
if not isinstance(email, str):
raise typeerror("email必须是字符串")
parts = email.split("@")
return len(parts) == 2 and all(parts) and "." in parts[1]
3.4 风格选型建议
# 💡 风格选择建议: # # google风格 → 推荐!最流行,可读性最好,vs code/pycharm原生支持 # numpy风格 → 科学计算/数据分析项目首选 # sphinx风格 → 用sphinx生成文档的项目,传统选择 # # 关键不是用哪种风格,而是在项目中保持一致!
四、docstring在实际开发中的应用
4.1 doctest:用docstring做测试
# doctest模块可以从docstring中提取>>>示例并自动测试!
def add(a: int, b: int) -> int:
"""返回两个整数的和。
>>> add(2, 3)
5
>>> add(-1, 1)
0
>>> add(0, 0)
0
"""
return a + b
def factorial(n: int) -> int:
"""计算n的阶乘。
>>> 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
if __name__ == "__main__":
import doctest
doctest.testmod()
print("所有doctest通过!")
4.2 模块和类的docstring
"""
用户管理模块
提供用户注册、登录、信息查询和管理功能。
classes:
user: 用户数据模型
usermanager: 用户管理服务
authenticationerror: 认证异常
functions:
create_user: 创建新用户
authenticate: 验证用户身份
usage:
>>> from user_management import create_user
>>> user = create_user("zhangsan", "password123",
... email="zs@test.com")
>>> print(user.name)
张三
"""
class usermanager:
"""用户管理服务类。
负责处理用户的创建、查询、更新和删除操作。
所有数据库操作通过此类统一管理。
attributes:
db_connection: 数据库连接对象
cache: 用户信息缓存
max_cache_size: 最大缓存条目数
example:
>>> manager = usermanager(db_conn)
>>> user = manager.get_user(123)
>>> manager.update_user(123, {"age": 30})
"""
def __init__(self, db_connection):
"""初始化用户管理器。"""
pass
五、总结
docstring是程序员给未来的自己(和同事)写的信。好的docstring让代码"自带说明书"。
核心要点:
- 所有公共函数/类/模块都应该有docstring
- pep 257是python docstring的官方规范
- google风格是目前最流行的选择
- doctest让docstring既是文档又是测试
- 一致性比风格更重要——项目内统一风格
docstring应该写什么:
- 函数做什么(不是怎么做)
- 参数的含义和类型
- 返回值的含义
- 可能抛出的异常
- 简单的使用示例
docstring不应该写什么:
- 显而易见的实现细节
- 版本历史(交给git)
- 谁写的、什么时候写的(交给git blame)
以上就是python中函数文档字符串docstring的编写规范详解的详细内容,更多关于python文档字符串的资料请关注代码网其它相关文章!
发表评论