Python代码注释三种写法与docstring调试技巧
一、注释不是写给机器的Python注释写在代码里但不参与执行,主要给阅读者看,包括几个月后的自己。原文提到一个常见现象:三个月后回头看自己没写注释的代码,往往已经想不起当时的思路。注释不是越多越好,而是要把“为什么这样写”讲清楚。
二、Python的三种注释写法
2.1 单行注释:#
以#开头,直到行尾的所有内容都被Python忽略。可以独占一行,也可以放在代码行尾。
# 这是一个单行注释
print('Hello, World!')# 行尾注释
total = 0
for i in range(1, 101):
total += i# 累加每一个数字
print(total)# 输出5050
单行注释也常用于临时禁用某行代码,调试时很直接:
print('这条会执行')
# print('这条不会执行,因为被注释掉了')
print('这条也会执行')
常见IDE里选中多行后按 Ctrl + /(Mac 为 Cmd + /)可以批量注释或取消注释。
2.2 三引号多行注释:''' 或 """
用三个单引号或三个双引号包裹的内容可以跨越多行,Python解释器会忽略它们。
'''
这是一个多行注释
可以跨越多行
'''
"""
这也是一个多行注释
双引号效果相同
"""
需要说明一个技术细节:三个引号在Python里实际创建的是字符串对象,只是没有赋值给任何变量,所以创建后马上被丢弃。严格说它不是专用注释语法,而是“被丢弃的字符串字面量”,但实际开发中都把它当多行注释用。三种写法也可以用于函数文档、模块说明头部、临时包住大段代码:
# 函数的文档字符串——最正式的用法
def calculate_area(length, width):
"""
计算矩形的面积。
参数:
length (float): 矩形的长度
width (float): 矩形的宽度
返回:
float: 矩形的面积
"""
return length * width
# 代码顶部的模块说明
'''
模块名:用户管理
功能:处理用户的注册、登录、信息修改等操作
作者:张三
日期:2025-05-30
版本:v1.0
'''
# 临时注释掉一大段代码
'''
print('这段代码暂时不需要执行')
print('先用三个引号把它包起来')
print('等需要的时候再解开')
'''
2.3 文档字符串(docstring)
docstring 写在函数、类或模块的第一行,用 """...""" 包裹。它和普通三引号注释的关键区别是:可以被程序读取。
def greet(name, greeting='你好'):
"""向指定的人打招呼。
Args:
name: 被问候的人的名字
greeting: 问候语,默认为'你好'
Returns:
str: 完整的问候语字符串
Examples:
>>> greet('小明')
'你好,小明!'
>>> greet('小红', '嗨')
'嗨,小红!'
"""
return f'{greeting},{name}!'
# 通过__doc__属性访问
print(greet.__doc__)
# 用help()查看格式化文档
help(greet)
对自定义函数和类花一分钟写docstring,后续维护成本会明显降低。
三、哪些场景必须写注释,哪些不用写
必须写注释的场景主要有五类。
第一类是解释“为什么”,而不是重复“是什么”。例如:
# 无意义:只是重复代码
x = x + 1# 将x加1
# 有价值:解释原因
x = x + 1# 补偿索引偏移,因为用户输入的序号从1开始而不是0
第二类是非显而易见的算法或逻辑。比如埃拉托斯特尼筛法中,需要说明为什么只检查到 sqrt(n):
# 使用埃拉托斯特尼筛法找出所有质数
def sieve_of_eratosthenes(n):
is_prime = * (n + 1)
is_prime = is_prime = False
# 只需要检查到sqrt(n),因为如果n是合数,
# 它必定有一个因子小于等于sqrt(n)
for i in range(2, int(n ** 0.5) + 1):
if is_prime:
for j in range(i * i, n + 1, i):
is_prime = False
return ]
第三类是带有特殊限制或前提条件的代码:
# 注意:这个函数假设输入列表已按升序排列
# 如果列表未排序,返回的结果将是错误的
def binary_search(sorted_list, target):
# ... 二分查找的实现
pass
第四类是解决特定bug的代码,比如跨平台路径问题:
import os
# 在Windows上,文件路径中的反斜杠需要转义
# 使用os.path.join可以避免平台差异
file_path = os.path.join('data', 'users', 'info.csv')
第五类是TODO、FIXME、HACK等标记:
# TODO: 这里的错误处理需要完善,目前只在理想情况下工作
# FIXME: 当用户名为空时会崩溃,需要添加空值检查
# HACK: 这是一个临时方案,等后端接口好了之后要重构
不需要写注释的场景也有三类。代码本身已经足够清晰时,比如 name = '小明' 后面再写“设置名字为小明”就是废话;函数名和变量名已经说明逻辑时,如 calculate_average_score(scores),额外注释反而冗余;一段复杂流程可以抽取成小函数时,函数名本身就是最好的注释:
# 拆分为小函数后,函数名说明了每一步
def process_order(order):
validate_order(order)
check_inventory(order)
deduct_inventory(order)
update_order_status(order, '已发货')
四、注释的黄金法则
第一条法则:注释解释“为什么”,代码说明“是什么”。坏注释重复代码,好注释解释业务意图。
# 坏注释:重复代码
for employee in employees:
# 计算工资
salary = employee.hours * employee.hourly_rate
# 打印工资
print(salary)
# 好注释:解释背后的规则
for employee in employees:
salary = employee.hours * employee.hourly_rate
# 根据公司政策,加班时间按1.5倍计算
if employee.hours > 40:
overtime_hours = employee.hours - 40
salary += overtime_hours * employee.hourly_rate * 0.5
print(salary)
第二条法则:注释必须和代码同步。过时的注释比没有注释更危险。
# 危险的过时注释:注释写2018年税率,代码实际已经变了
def calculate_tax(income):
# 使用2018年的税率(实际上2025年已经改了!)
if income < 5000:
return 0
elif income < 8000:
return income * 0.03
# ...
# 更好的做法:把税率表写成数据,代码本身表达逻辑
TAX_BRACKETS_2025 = [
(0, 5000, 0),
(5000, 8000, 0.03),
(8000, 17000, 0.10),
# ...
]
def calculate_tax(income):
for lower, upper, rate in TAX_BRACKETS_2025:
if lower <= income < upper:
return (income - lower) * rate
第三条法则:注释用中文还是英文。原文建议:个人项目和学习笔记用中文更顺畅;团队项目和开源项目遵循已有规范,通常建议英文以方便国际协作;docstring 如果项目可能开源,建议中英文都写或直接写英文。
# 个人学习项目——中文注释完全OK
def binary_search(arr, target):
"""二分查找算法"""
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr == target:
return mid# 找到了
elif arr < target:
left = mid + 1# 目标在右半部分
else:
right = mid - 1# 目标在左半部分
return -1# 没找到
五、实战对比:一段筛选代码的注释改造
没有注释的版本:
def f(d, p):
r = []
for k, v in d.items():
if p(v):
r.append(k)
return r
data = {'a': 85, 'b': 42, 'c': 96, 'd': 58, 'e': 73}
print(f(data, lambda x: x >= 60))
一眼不容易看出函数用途。加上模块说明、docstring、参数说明和行内注释后:
"""
学生成绩筛选程序
功能:从学生成绩字典中筛选出及格(>=60分)的学生名单
"""
def filter_by_criteria(data_dict, check_function):
"""
根据指定的筛选条件,从字典中筛选出符合条件的键。
参数:
data_dict (dict): 待筛选的字典,键为学生名,值为成绩
check_function (callable): 筛选函数,接受一个值,返回True/False
返回:
list: 符合条件的键(学生名)列表
示例:
>>> scores = {'小明': 85, '小红': 42}
>>> filter_by_criteria(scores, lambda x: x >= 60)
['小明']
"""
passed_keys = []# 存储符合条件的学生名
for key, value in data_dict.items():
if check_function(value):
passed_keys.append(key)# 该学生成绩符合条件,加入结果
return passed_keys
# 学生成绩数据
student_scores = {
'小明': 85,
'小红': 42,
'小刚': 96,
'小丽': 58,
'小华': 73
}
# 筛选条件:成绩大于等于60分(及格线)
def is_passing(score):
return score >= 60
# 执行筛选并输出结果
passing_students = filter_by_criteria(student_scores, is_passing)
print(f'及格的学生有:{passing_students}')
print(f'及格人数:{len(passing_students)}人')
print(f'不及格人数:{len(student_scores) - len(passing_students)}人')
行数变多了,但即使是从没看过这段代码的开发者,也能立即理解它在做什么。
六、和其他语言注释语法对照
Python:# 单行注释,''' 或 """ 多行注释。
C/C++/Java:// 单行注释,/* */ 多行注释。
JavaScript:// 单行注释,/* */ 多行注释。
SQL:-- 单行注释,/* */ 多行注释。
Bash/Shell:# 单行注释,: '注释' 形式。
HTML:没有单行注释,使用 <!-- --> 注释。
Python没有像C/Java那样专门的多行注释语法,而是把字符串字面量复用作多行注释,这一点体现了它“少即是多”的设计取向。
七、注释在调试中的两个实用技巧
第一,逐段排查bug。当程序出问题时,可以用注释快速隔离可疑段落:
def complex_calculation(data):
# 第一步:数据清洗
cleaned_data = clean_data(data)
print(f'清洗后数据条数:{len(cleaned_data)}')
# 第二步:数据转换(怀疑这里有bug,先注释掉后面,只看前面的输出)
# transformed_data = transform_data(cleaned_data)
# print(f'转换后数据条数:{len(transformed_data)}')
# 第三步:计算
# result = calculate(transformed_data)
# return result
# 暂时返回None,等排查完bug再恢复
return None
第二,用注释做“版本对比”。学习和实验阶段,可以保留多种写法,对比可读性和执行结果:
# 写法一:使用列表推导式
# squared =
# 写法二:使用map函数
# squared = list(map(lambda x: x**2, range(10)))
# 写法三:传统for循环——当前采用这种写法,最易读
squared = []
for x in range(10):
squared.append(x ** 2)
print(squared)
八、小结
注释是写给人的,不是写给机器的。机器执行时会忽略注释,但几个月后的自己会感谢今天留下的说明。核心要点可以归纳为:掌握 #、'''/"""、docstring 三种写法;注释解释“为什么”而不是重复“是什么”;注释和代码必须同步,过时注释比没有注释更危险;好的命名可以替代一部分注释;关键算法、业务规则、特殊限制和已知问题必须写清楚。下一篇可以继续关注Python缩进规则和代码块规范。
Re: Python代码注释三种写法与docstring调试技巧
这篇整理得很清楚,尤其是把三引号其实是被丢弃的字符串字面量、docstring 能被程序读取这个区别点出来了,这点很容易混。注释的核心不是多,而是解释为什么,这个观点很赞同。像索引偏移、只检查到 sqrt(n)、输入已排序前提、跨平台路径、TODO/FIXME/HACK 这些场景,都是实际写代码时容易忘掉上下文的地方,留一句能省很多时间。不需要注释的三类也说到点子上,代码和命名能表达清楚时就别写废话,复杂流程抽成小函数往往比堆注释更好。Ctrl + / 批量注释这个技巧也很实用。整体很有帮助,感谢分享。Re: Python代码注释三种写法与docstring调试技巧
这篇总结很实用,把三种写法、docstring 和注释场景都讲清楚了。以前我也容易把三引号直接当成多行注释,后来才知道它本质上是没赋值的字符串字面量,只是不参与执行,这个细节确实值得注意。docstring 那部分尤其有用,写在函数第一行之后,既能用 __doc__ 查看,也能用 help 显示格式化文档,后面维护时省事很多。 注释“为什么”而不是“是什么”这点很赞同。像索引偏移、二分查找要求有序、跨平台路径、TODO/FIXME/HACK 这些,不写原因以后很容易忘。代码本身清楚时就不用硬加注释,把复杂流程拆成小函数,函数名本身就是说明。IDE 里 Ctrl + / 批量注释也很顺手,调试时比直接删代码稳妥。整体看下来,注释关键还是写到点上,不是越多越好。Re: Python代码注释三种写法与docstring调试技巧
这篇对注释的三种写法讲得挺全,尤其是把三引号注释和 docstring 的区别说清楚了。三引号实际创建的是字符串对象,只是没赋值所以被丢弃,这个细节很多人容易忽略;而 docstring 能被 __doc__ 和 help() 读取,写函数和类时确实值得顺手加上。 单行注释用来做行尾说明和临时禁用代码很顺手,三引号临时包住大段代码在调试时也很实用。关于哪些场景必须写注释、哪些不用写,总结得也很接地气,特别是解释“为什么”、非显而易见算法、特殊前提和 TODO、FIXME、HACK 这些,比单纯重复代码有价值多了。代码清晰、命名到位、拆成小函数后不再硬加注释,这点也很认同。整体受用,期待后续 docstring 调试技巧的内容。
页:
[1]