查看: 1145|回复: 3

Python代码注释三种写法与docstring调试技巧

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
一、注释不是写给机器的

Python注释写在代码里但不参与执行,主要给阅读者看,包括几个月后的自己。原文提到一个常见现象:三个月后回头看自己没写注释的代码,往往已经想不起当时的思路。注释不是越多越好,而是要把“为什么这样写”讲清楚。

二、Python的三种注释写法

2.1 单行注释:#

以#开头,直到行尾的所有内容都被Python忽略。可以独占一行,也可以放在代码行尾。
  1. # 这是一个单行注释
  2. print('Hello, World!')  # 行尾注释
  3. total = 0
  4. for i in range(1, 101):
  5.     total += i  # 累加每一个数字
  6. print(total)  # 输出5050
复制代码

单行注释也常用于临时禁用某行代码,调试时很直接:
  1. print('这条会执行')
  2. # print('这条不会执行,因为被注释掉了')
  3. print('这条也会执行')
复制代码

常见IDE里选中多行后按 Ctrl + /(Mac 为 Cmd + /)可以批量注释或取消注释。

2.2 三引号多行注释:''' 或 """

用三个单引号或三个双引号包裹的内容可以跨越多行,Python解释器会忽略它们。
  1. '''
  2. 这是一个多行注释
  3. 可以跨越多行
  4. '''
  5. """
  6. 这也是一个多行注释
  7. 双引号效果相同
  8. """
复制代码

需要说明一个技术细节:三个引号在Python里实际创建的是字符串对象,只是没有赋值给任何变量,所以创建后马上被丢弃。严格说它不是专用注释语法,而是“被丢弃的字符串字面量”,但实际开发中都把它当多行注释用。三种写法也可以用于函数文档、模块说明头部、临时包住大段代码:
  1. # 函数的文档字符串——最正式的用法
  2. def calculate_area(length, width):
  3.     """
  4.     计算矩形的面积。
  5.     参数:
  6.         length (float): 矩形的长度
  7.         width (float): 矩形的宽度
  8.     返回:
  9.         float: 矩形的面积
  10.     """
  11.     return length * width
  12. # 代码顶部的模块说明
  13. '''
  14. 模块名:用户管理
  15. 功能:处理用户的注册、登录、信息修改等操作
  16. 作者:张三
  17. 日期:2025-05-30
  18. 版本:v1.0
  19. '''
  20. # 临时注释掉一大段代码
  21. '''
  22. print('这段代码暂时不需要执行')
  23. print('先用三个引号把它包起来')
  24. print('等需要的时候再解开')
  25. '''
复制代码

2.3 文档字符串(docstring)

docstring 写在函数、类或模块的第一行,用 """...""" 包裹。它和普通三引号注释的关键区别是:可以被程序读取。
  1. def greet(name, greeting='你好'):
  2.     """向指定的人打招呼。
  3.     Args:
  4.         name: 被问候的人的名字
  5.         greeting: 问候语,默认为'你好'
  6.     Returns:
  7.         str: 完整的问候语字符串
  8.     Examples:
  9.         >>> greet('小明')
  10.         '你好,小明!'
  11.         >>> greet('小红', '嗨')
  12.         '嗨,小红!'
  13.     """
  14.     return f'{greeting},{name}!'
  15. # 通过__doc__属性访问
  16. print(greet.__doc__)
  17. # 用help()查看格式化文档
  18. help(greet)
复制代码

对自定义函数和类花一分钟写docstring,后续维护成本会明显降低。

三、哪些场景必须写注释,哪些不用写

必须写注释的场景主要有五类。

第一类是解释“为什么”,而不是重复“是什么”。例如:
  1. # 无意义:只是重复代码
  2. x = x + 1  # 将x加1
  3. # 有价值:解释原因
  4. x = x + 1  # 补偿索引偏移,因为用户输入的序号从1开始而不是0
复制代码

第二类是非显而易见的算法或逻辑。比如埃拉托斯特尼筛法中,需要说明为什么只检查到 sqrt(n):
  1. # 使用埃拉托斯特尼筛法找出所有质数
  2. def sieve_of_eratosthenes(n):
  3.     is_prime = [True] * (n + 1)
  4.     is_prime[0] = is_prime[1] = False
  5.     # 只需要检查到sqrt(n),因为如果n是合数,
  6.     # 它必定有一个因子小于等于sqrt(n)
  7.     for i in range(2, int(n ** 0.5) + 1):
  8.         if is_prime[i]:
  9.             for j in range(i * i, n + 1, i):
  10.                 is_prime[j] = False
  11.     return [i for i in range(2, n + 1) if is_prime[i]]
复制代码

第三类是带有特殊限制或前提条件的代码:
  1. # 注意:这个函数假设输入列表已按升序排列
  2. # 如果列表未排序,返回的结果将是错误的
  3. def binary_search(sorted_list, target):
  4.     # ... 二分查找的实现
  5.     pass
复制代码

第四类是解决特定bug的代码,比如跨平台路径问题:
  1. import os
  2. # 在Windows上,文件路径中的反斜杠需要转义
  3. # 使用os.path.join可以避免平台差异
  4. file_path = os.path.join('data', 'users', 'info.csv')
复制代码

第五类是TODO、FIXME、HACK等标记:
  1. # TODO: 这里的错误处理需要完善,目前只在理想情况下工作
  2. # FIXME: 当用户名为空时会崩溃,需要添加空值检查
  3. # HACK: 这是一个临时方案,等后端接口好了之后要重构
复制代码

不需要写注释的场景也有三类。代码本身已经足够清晰时,比如 name = '小明' 后面再写“设置名字为小明”就是废话;函数名和变量名已经说明逻辑时,如 calculate_average_score(scores),额外注释反而冗余;一段复杂流程可以抽取成小函数时,函数名本身就是最好的注释:
  1. # 拆分为小函数后,函数名说明了每一步
  2. def process_order(order):
  3.     validate_order(order)
  4.     check_inventory(order)
  5.     deduct_inventory(order)
  6.     update_order_status(order, '已发货')
复制代码

四、注释的黄金法则

第一条法则:注释解释“为什么”,代码说明“是什么”。坏注释重复代码,好注释解释业务意图。
  1. # 坏注释:重复代码
  2. for employee in employees:
  3.     # 计算工资
  4.     salary = employee.hours * employee.hourly_rate
  5.     # 打印工资
  6.     print(salary)
  7. # 好注释:解释背后的规则
  8. for employee in employees:
  9.     salary = employee.hours * employee.hourly_rate
  10.     # 根据公司政策,加班时间按1.5倍计算
  11.     if employee.hours > 40:
  12.         overtime_hours = employee.hours - 40
  13.         salary += overtime_hours * employee.hourly_rate * 0.5
  14.     print(salary)
复制代码

第二条法则:注释必须和代码同步。过时的注释比没有注释更危险。
  1. # 危险的过时注释:注释写2018年税率,代码实际已经变了
  2. def calculate_tax(income):
  3.     # 使用2018年的税率(实际上2025年已经改了!)
  4.     if income < 5000:
  5.         return 0
  6.     elif income < 8000:
  7.         return income * 0.03
  8.     # ...
  9. # 更好的做法:把税率表写成数据,代码本身表达逻辑
  10. TAX_BRACKETS_2025 = [
  11.     (0, 5000, 0),
  12.     (5000, 8000, 0.03),
  13.     (8000, 17000, 0.10),
  14.     # ...
  15. ]
  16. def calculate_tax(income):
  17.     for lower, upper, rate in TAX_BRACKETS_2025:
  18.         if lower <= income < upper:
  19.             return (income - lower) * rate
复制代码

第三条法则:注释用中文还是英文。原文建议:个人项目和学习笔记用中文更顺畅;团队项目和开源项目遵循已有规范,通常建议英文以方便国际协作;docstring 如果项目可能开源,建议中英文都写或直接写英文。
  1. # 个人学习项目——中文注释完全OK
  2. def binary_search(arr, target):
  3.     """二分查找算法"""
  4.     left, right = 0, len(arr) - 1
  5.     while left <= right:
  6.         mid = (left + right) // 2
  7.         if arr[mid] == target:
  8.             return mid  # 找到了
  9.         elif arr[mid] < target:
  10.             left = mid + 1  # 目标在右半部分
  11.         else:
  12.             right = mid - 1  # 目标在左半部分
  13.     return -1  # 没找到
复制代码

五、实战对比:一段筛选代码的注释改造

没有注释的版本:
  1. def f(d, p):
  2.     r = []
  3.     for k, v in d.items():
  4.         if p(v):
  5.             r.append(k)
  6.     return r
  7. data = {'a': 85, 'b': 42, 'c': 96, 'd': 58, 'e': 73}
  8. print(f(data, lambda x: x >= 60))
复制代码

一眼不容易看出函数用途。加上模块说明、docstring、参数说明和行内注释后:
  1. """
  2. 学生成绩筛选程序
  3. 功能:从学生成绩字典中筛选出及格(>=60分)的学生名单
  4. """
  5. def filter_by_criteria(data_dict, check_function):
  6.     """
  7.     根据指定的筛选条件,从字典中筛选出符合条件的键。
  8.     参数:
  9.         data_dict (dict): 待筛选的字典,键为学生名,值为成绩
  10.         check_function (callable): 筛选函数,接受一个值,返回True/False
  11.     返回:
  12.         list: 符合条件的键(学生名)列表
  13.     示例:
  14.         >>> scores = {'小明': 85, '小红': 42}
  15.         >>> filter_by_criteria(scores, lambda x: x >= 60)
  16.         ['小明']
  17.     """
  18.     passed_keys = []  # 存储符合条件的学生名
  19.     for key, value in data_dict.items():
  20.         if check_function(value):
  21.             passed_keys.append(key)  # 该学生成绩符合条件,加入结果
  22.     return passed_keys
  23. # 学生成绩数据
  24. student_scores = {
  25.     '小明': 85,
  26.     '小红': 42,
  27.     '小刚': 96,
  28.     '小丽': 58,
  29.     '小华': 73
  30. }
  31. # 筛选条件:成绩大于等于60分(及格线)
  32. def is_passing(score):
  33.     return score >= 60
  34. # 执行筛选并输出结果
  35. passing_students = filter_by_criteria(student_scores, is_passing)
  36. print(f'及格的学生有:{passing_students}')
  37. print(f'及格人数:{len(passing_students)}人')
  38. print(f'不及格人数:{len(student_scores) - len(passing_students)}人')
复制代码

行数变多了,但即使是从没看过这段代码的开发者,也能立即理解它在做什么。

六、和其他语言注释语法对照

Python:# 单行注释,''' 或 """ 多行注释。
C/C++/Java:// 单行注释,/* */ 多行注释。
JavaScript:// 单行注释,/* */ 多行注释。
SQL:-- 单行注释,/* */ 多行注释。
Bash/Shell:# 单行注释,: '注释' 形式。
HTML:没有单行注释,使用 <!-- --> 注释。

Python没有像C/Java那样专门的多行注释语法,而是把字符串字面量复用作多行注释,这一点体现了它“少即是多”的设计取向。

七、注释在调试中的两个实用技巧

第一,逐段排查bug。当程序出问题时,可以用注释快速隔离可疑段落:
  1. def complex_calculation(data):
  2.     # 第一步:数据清洗
  3.     cleaned_data = clean_data(data)
  4.     print(f'清洗后数据条数:{len(cleaned_data)}')
  5.     # 第二步:数据转换(怀疑这里有bug,先注释掉后面,只看前面的输出)
  6.     # transformed_data = transform_data(cleaned_data)
  7.     # print(f'转换后数据条数:{len(transformed_data)}')
  8.     # 第三步:计算
  9.     # result = calculate(transformed_data)
  10.     # return result
  11.     # 暂时返回None,等排查完bug再恢复
  12.     return None
复制代码

第二,用注释做“版本对比”。学习和实验阶段,可以保留多种写法,对比可读性和执行结果:
  1. # 写法一:使用列表推导式
  2. # squared = [x**2 for x in range(10)]
  3. # 写法二:使用map函数
  4. # squared = list(map(lambda x: x**2, range(10)))
  5. # 写法三:传统for循环——当前采用这种写法,最易读
  6. squared = []
  7. for x in range(10):
  8.     squared.append(x ** 2)
  9. print(squared)
复制代码

八、小结

注释是写给人的,不是写给机器的。机器执行时会忽略注释,但几个月后的自己会感谢今天留下的说明。核心要点可以归纳为:掌握 #、'''/"""、docstring 三种写法;注释解释“为什么”而不是重复“是什么”;注释和代码必须同步,过时注释比没有注释更危险;好的命名可以替代一部分注释;关键算法、业务规则、特殊限制和已知问题必须写清楚。下一篇可以继续关注Python缩进规则和代码块规范。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python代码注释三种写法与docstring调试技巧

这篇整理得很清楚,尤其是把三引号其实是被丢弃的字符串字面量、docstring 能被程序读取这个区别点出来了,这点很容易混。注释的核心不是多,而是解释为什么,这个观点很赞同。像索引偏移、只检查到 sqrt(n)、输入已排序前提、跨平台路径、TODO/FIXME/HACK 这些场景,都是实际写代码时容易忘掉上下文的地方,留一句能省很多时间。不需要注释的三类也说到点子上,代码和命名能表达清楚时就别写废话,复杂流程抽成小函数往往比堆注释更好。Ctrl + / 批量注释这个技巧也很实用。整体很有帮助,感谢分享。
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: Python代码注释三种写法与docstring调试技巧

这篇总结很实用,把三种写法、docstring 和注释场景都讲清楚了。以前我也容易把三引号直接当成多行注释,后来才知道它本质上是没赋值的字符串字面量,只是不参与执行,这个细节确实值得注意。docstring 那部分尤其有用,写在函数第一行之后,既能用 __doc__ 查看,也能用 help 显示格式化文档,后面维护时省事很多。 注释“为什么”而不是“是什么”这点很赞同。像索引偏移、二分查找要求有序、跨平台路径、TODO/FIXME/HACK 这些,不写原因以后很容易忘。代码本身清楚时就不用硬加注释,把复杂流程拆成小函数,函数名本身就是说明。IDE 里 Ctrl + / 批量注释也很顺手,调试时比直接删代码稳妥。整体看下来,注释关键还是写到点上,不是越多越好。
回复 支持 反对

使用道具 举报

发表于 昨天 19:20 | 显示全部楼层

Re: Python代码注释三种写法与docstring调试技巧

这篇对注释的三种写法讲得挺全,尤其是把三引号注释和 docstring 的区别说清楚了。三引号实际创建的是字符串对象,只是没赋值所以被丢弃,这个细节很多人容易忽略;而 docstring 能被 __doc__ 和 help() 读取,写函数和类时确实值得顺手加上。 单行注释用来做行尾说明和临时禁用代码很顺手,三引号临时包住大段代码在调试时也很实用。关于哪些场景必须写注释、哪些不用写,总结得也很接地气,特别是解释“为什么”、非显而易见算法、特殊前提和 TODO、FIXME、HACK 这些,比单纯重复代码有价值多了。代码清晰、命名到位、拆成小函数后不再硬加注释,这点也很认同。整体受用,期待后续 docstring 调试技巧的内容。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-10 06:10 , Processed in 0.041274 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部