写代码时,注释往往被当作可有可无的东西。但真正维护过项目的人都知道,一段没有注释的代码,隔几个月再回头看,很可能连自己都看不懂。注释不是写给机器看的,而是写给下一个读代码的人——包括未来的你。好的注释能解释代码背后的意图,让维护成本大幅降低。Python提供了三种常用的注释形式,下面逐一说明。
一、单行注释:井号
单行注释以 # 开头,从 # 到行尾的内容都会被视为注释,Python解释器会直接忽略。这是最常用、最轻量的注释方式。
- # 这是一个单行注释
- print('Hello, World!') # 这行打印一句话
- # 计算1到100的和
- total = 0
- for i in range(1, 101):
- total += i # 累加每一个数字
- print(total) # 输出结果:5050
复制代码
单行注释还有一个实用场景:临时禁用某一行代码。调试时,把不需要执行的代码注释掉,比删掉再恢复更安全。
- print('这条会执行')
- # print('这条不会执行,因为被注释掉了')
- print('这条也会执行')
复制代码
在主流IDE中,选中多行后按 Ctrl+(Windows/Linux)或 Cmd+(Mac)可以快速注释或取消注释,配合单行注释调试非常方便。
二、多行注释:三引号
Python没有专门的多行注释语法。一种常见的做法是用三个单引号 ''' 或三个双引号 """ 将内容包裹起来,实现类似多行注释的效果。
- '''
- 这是一个多行注释
- 可以跨越多行
- Python解释器会忽略这些内容
- '''
- """
- 这也是一个多行注释
- 用双引号也是一样的效果
- """
复制代码
需要注意的是,三引号包裹的内容本质上是一个字符串字面量,只是它没有被赋值给任何变量,所以解释器创建之后马上丢弃。严格来说,它不是真正的注释,而是“被丢弃的字符串字面量”。但实际开发中,大家都把它当作多行注释使用。
三引号注释常用于三种场景:函数或模块顶部的说明、临时注释掉一大段代码、以及编写文档字符串。
三、文档字符串(docstring)
文档字符串是Python中特殊的“注释”形式,用三引号包裹,写在模块、类或函数的第一行。与普通注释最大的区别是,文档字符串可以被程序动态访问。
- def greet(name, greeting='你好'):
- '''向指定的人打招呼。
- Args:
- name: 被问候的人的名字
- greeting: 问候语,默认为“你好”
- Returns:
- str: 完整的问候语字符串
- '''
- return f'{greeting},{name}!'
- # 通过 __doc__ 属性访问文档字符串
- print(greet.__doc__)
- # 也可以用 help() 查看格式化的文档
- help(greet)
复制代码
有了文档字符串,函数和类的使用者不必阅读实现细节,就能清楚知道参数的格式和返回值。编写自定义函数或类时,花一分钟写一个简短docstring,长期收益很大。
四、什么时候该写注释
不是所有代码都需要注释。判断标准很简单:注释应该解释“为什么”,而不是重复“是什么”。
无意义的注释只会产生噪音:
有价值的注释解释了原因:- x = x + 1 # 补偿索引偏移,因为用户输入的序号从1开始而不是0
复制代码
以下场景最好写注释:
1. 算法逻辑不直观时。例如埃拉托斯特尼筛法,需要说明为什么只检查到 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]]
复制代码
2. 函数存在特殊限制或前置条件。比如二分查找要求输入列表已排序,注释可以提醒调用者。
3. 针对特定bug的修复代码。比如为了避免平台差异而使用 os.path.join,这时注释能解释为什么不用硬编码路径。
4. 使用 TODO、FIXME、HACK 标记待办事项或临时方案。
- # TODO: 这里的错误处理需要完善,目前只在理想情况下工作
- # FIXME: 当用户名为空时会崩溃,需要添加空值检查
- # HACK: 这是一个临时方案,等后端接口好了之后要重构
复制代码
相反,如果代码本身已经很清晰,或者可以通过良好命名表达意图,就不需要额外注释。比如函数名 calculate_average_score,一眼就能看出是计算平均分,不必再加注释。一段复杂逻辑如果能拆分成多个小函数,函数名本身就是最好的注释。
五、注释的黄金法则
核心原则是:注释解释“为什么”,代码说明“是什么”。好的注释能补充代码之外的背景信息,比如业务规则、性能考虑和已知问题。
另一个重要原则是:注释必须与代码保持同步。过时的注释比没有注释更危险,因为它会误导后来的维护者。与其修改注释,不如将逻辑写得更清晰,例如用常量表代替固定税率,代码本身就能说明策略。
关于注释用中文还是英文,取决于场景。个人项目和内部团队项目,用中文沟通成本更低;开源项目或国际化团队,通常用英文;如果项目可能开源,docstring建议中英双语或直接用英文。
六、实战:有注释和无注释的差异
看一个没有注释的函数:
- 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))
复制代码
这个函数干了什么?需要仔细读才能明白。加上良好的命名和注释后:
- def filter_by_criteria(data_dict, check_function):
- '''根据指定的筛选条件,从字典中筛选出符合条件的键。
- 参数:
- data_dict (dict): 待筛选的字典,键为学生名,值为成绩
- check_function (callable): 筛选函数,接受一个值,返回True/False
- 返回:
- list: 符合条件的键(学生名)列表
- '''
- 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)}人')
复制代码
虽然行数变多了,但可读性大幅提升,即使换一个人接手也能快速理解逻辑。
七、注释在调试中的妙用
注释也是调试时的利器。当程序出现问题时,可以用单行注释暂时屏蔽部分代码,逐步定位问题。
- 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)}')
- # 第三步:暂时返回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)
复制代码
八、小结
Python中三种注释形式各有定位:单行注释适合解释局部逻辑;三引号适合多行说明或临时屏蔽代码;docstring则面向工具和API文档,是正规的文档接口。写注释时,多问自己“这里为什么这样写”,而不是“这里写了什么”。好的命名能替代一部分注释,但关键逻辑、业务规则和已知问题,仍然需要注释来记录。保持注释与代码同步,过时的注释应该及时修正或删除。从今天开始,在写代码时把注释当成代码的一部分,长期维护时会受益无穷。 |