014、注释与 PEP8:写出让人读得懂、AI 抄得对的 Python 代码
014、注释与 PEP8:写出让人读得懂、AI 抄得对的 Python 代码
上周五晚上十一点,我被一个线上告警电话从床上拽起来。一个跑了三个月的数据处理脚本突然报错,日志里只有一行“IndexError: list index out of range”。我打开代码,看到这样的注释:
# 处理数据foriinrange(len(data)):result=data[i][2]# 取第三个字段三个月前写这段代码的人已经离职了。我盯着那个魔法数字“2”,不知道它代表什么字段。更糟糕的是,data 的结构在某个版本更新后变了,索引 2 变成了空值。如果当初注释写清楚“这是用户手机号”,或者用常量代替魔法数字,我至少能少花两小时定位问题。
这个场景让我意识到:注释和代码规范不是写给编译器看的,是写给三个月后的自己、接手你代码的同事、以及越来越常见的 AI 代码助手看的。AI 模型训练时大量抓取 GitHub 上的 Python 代码,如果你的代码注释混乱、风格不一致,AI 抄出来的代码也会带着同样的毛病。
注释:别写“是什么”,写“为什么”
很多初学者喜欢写这种注释:
x=x+1# 把x加1这种注释等于没写。代码本身已经表达了“做什么”,注释应该解释“为什么这么做”。我踩过最深的坑是一个汇率转换函数:
defconvert_currency(amount,rate):# 这里乘以100再除以100是为了避免浮点数精度问题# 之前线上出现过0.1+0.2不等于0.3的bugreturnround(amount*rate*100)/100如果没有那段注释,后来维护的人可能会觉得“多此一举”直接删掉乘除操作,然后线上又炸一次。
写注释的黄金法则是:假设读代码的人知道 Python 语法,但不知道你的业务逻辑和踩过的坑。比如:
# 坏注释foriinrange(24):# 循环24次# 好注释forhourinrange(24):# 遍历一天24小时,生成每小时的数据快照再比如处理用户输入时:
# 坏注释iflen(name)>0:# 检查名字长度# 好注释iflen(name)>0:# 防止空字符串导致后续数据库查询报错,之前遇到过空用户名写入失败文档字符串:给函数写说明书
单行注释解决的是“这里为什么这么写”,文档字符串(docstring)解决的是“这个函数怎么用”。我见过最离谱的代码是一个 200 行的函数,没有任何文档字符串,参数名全是 a、b、c。三个月后原作者自己都看不懂了。
标准写法是这样的:
defcalculate_discount(price,user_level,is_vip=False):""" 根据用户等级和VIP状态计算折扣后的价格 参数: price (float): 原始价格,单位元 user_level (int): 用户等级,1-5级 is_vip (bool, optional): 是否为VIP用户,默认False 返回: float: 折扣后的价格 示例: >>> calculate_discount(100, 3, True) 85.0 """discount_map={1:0.9,2:0.85,3:0.8,4:0.75,5:0.7}base_discount=discount_map.get(user_level,1.0)ifis_vip:base_discount*=0.95returnround(price*base_discount,2)注意那个示例部分,用 doctest 格式写。这样既能当文档,又能直接跑测试。我习惯在写完函数后立刻补上文档字符串,因为这时候思路最清晰。等过几天再补,大概率会忘掉某些边界情况。
PEP8:不是教条,是团队协作的底线
PEP8 是 Python 的官方编码规范,但很多人把它当成“必须遵守的规则”,其实更应该理解为“减少沟通成本的约定”。我见过最极端的例子:一个团队里有人用 2 空格缩进,有人用 4 空格,有人用 Tab,合并代码时 diff 全是缩进变化,真正的逻辑改动反而被淹没了。
几个最容易踩坑的点:
行长度:PEP8 建议每行不超过 79 字符。别笑,我见过有人写一行 300 字符的列表推导式,调试时根本看不清哪里报错。如果一行太长,用括号隐式换行:
# 别这样写result=some_function(param1,param2,param3,param4,param5,param6,param7)# 这样写result=some_function(param1,param2,param3,param4,param5,param6,param7)空行:函数之间用两个空行,类方法之间用一个空行。这不是强迫症,是视觉分组。想象一下你在一堆文字里找某个函数定义,空行就是路标。
导入顺序:标准库、第三方库、本地模块,每组之间空一行。这样一眼就能看出依赖关系:
importosimportsysimportrequestsimportpandasaspdfrommyproject.utilsimportformat_date变量命名:别用单字母变量名,除非是循环计数器。我见过一个函数里用了 a、b、c、d、e、f 六个单字母变量,调试时根本分不清谁是谁。好的命名应该让人一看就知道这个变量存的是什么:
# 坏d=pd.read_csv('data.csv')f=d[d['age']>18]r=f.groupby('city').mean()# 好raw_data=pd.read_csv('data.csv')adult_data=raw_data[raw_data['age']>18]city_stats=adult_data.groupby('city').mean()让 AI 抄得对:注释就是训练数据
现在很多团队用 AI 辅助写代码,比如 GitHub Copilot、Cursor 之类的工具。这些 AI 模型训练时大量抓取公开代码库,如果你的代码注释清晰、风格规范,AI 生成代码时就会模仿这种风格。反过来,如果你的代码全是魔法数字、没有注释、命名混乱,AI 也会“学坏”。
我做过一个实验:在一个函数里写清楚文档字符串和注释,然后让 AI 补全类似功能的函数。AI 生成的代码自动带上了文档字符串和类型注解。另一个函数我故意不写注释,AI 生成的代码也光秃秃的。
所以,写规范的注释和代码,不仅是为了人类同事,也是在“训练”你身边的 AI 助手。你写得越好,AI 给你的建议就越靠谱。
个人经验:养成肌肉记忆
说了这么多,最后分享几个我自己的习惯:
- 写完函数立刻写文档字符串,哪怕只写一行。等回头再补,大概率会忘。
- 魔法数字必须加注释或改成常量。我吃过太多亏了,现在看到代码里有裸数字就浑身难受。
- 用 linter 自动检查。我本地配了 flake8 和 black,保存代码时自动格式化。别靠肉眼检查 PEP8,人眼会疲劳,机器不会。
- 注释里可以写“为什么不用另一种方案”。比如“这里没用列表推导式是因为可读性太差”,这种注释能帮后来的人避免重复踩坑。
代码是写给人看的,顺便让机器执行。注释和规范就是让“人”这部分更顺畅。三个月后的你,会感谢现在认真写注释的自己。
