一、注释不是写给机器的
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 = [True] * (n + 1)
- is_prime[0] = is_prime[1] = False
- # 只需要检查到sqrt(n),因为如果n是合数,
- # 它必定有一个因子小于等于sqrt(n)
- for i in range(2, int(n ** 0.5) + 1):
- if is_prime[i]:
- for j in range(i * i, n + 1, i):
- is_prime[j] = False
- return [i for i in range(2, n + 1) if is_prime[i]]
复制代码
第三类是带有特殊限制或前提条件的代码:
- # 注意:这个函数假设输入列表已按升序排列
- # 如果列表未排序,返回的结果将是错误的
- 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[mid] == target:
- return mid # 找到了
- elif arr[mid] < 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 = [x**2 for x in range(10)]
- # 写法二:使用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缩进规则和代码块规范。 |