ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PEP 8实战指南:从缩进到自动化工具,打造高可读性Python代码

PEP 8实战指南:从缩进到自动化工具,打造高可读性Python代码 先给你看两段功能完全一样的代码都是计算一个列表里所有偶数的平方和。第一段是新手常见的写法第二段做了风格调整你感受一下差别# 写法一 def calc(nums): result0 for i in nums: if i%20: resulti*i return result # 写法二 def calc_even_squares(numbers): total 0 for number in numbers: if number % 2 0: total number ** 2 return total如果你觉得第二段读起来更舒服那说明你已经体会到了PEP 8的价值。如果你觉得两段差不多那这篇内容就是写给你的。PEP 8是Python官方的代码风格规范全称是Python Enhancement Proposal 8由Guido van Rossum在2001年撰写目的是让所有Python代码保持统一的风格。很多刚入门的朋友会觉得这东西是形式主义代码能跑不就行了但等你需要读别人的代码、和别人协作、或者三个月后回看自己写的代码时就会明白风格规范不是束缚而是帮你省时间的工具。这篇文章会从零开始把PEP 8里最核心、最实用的规则拆开讲清楚配套解释每条规则背后的原因最后再聊聊现在主流的自动化工具怎么帮你守住这些规矩。1. PEP 8不是考试大纲是沟通协议1.1 风格规范存在的真正原因我先说一个很多人没意识到的点代码写出来首先是给人看的其次才是给机器执行的。机器只需要语法正确就能运行但人需要在代码里读出来逻辑、意图、边界情况甚至通过代码风格判断这段代码的作者专不专业。Python是出了名的可读性优先语言。它的设计哲学里有一条叫Readability counts可读性很重要。这也是为什么Python用缩进而不是花括号来划分代码块——强制你写出结构清晰、对齐整齐的代码。PEP 8在这个基础上更进一步把变量命名、空格使用、注释写法、导入顺序这些细节统一起来让所有Python代码看起来像出自同一个人之手。你可以把它理解成团队协作里的普通话标准。如果每个人都用自己的方言写代码你读别人的代码就像在听外语。统一风格之后换项目、换团队、看开源代码的成本都会大幅降低。1.2 为什么PEP 8不强制你却应该遵守严格来说Python解释器不会因为你违反PEP 8就报错。缩进用两个空格也能跑函数名用大写开头也能跑行长超过100字符照样运行正常。所以很多初学者会有一个疑问既然不报错为什么还要遵守原因是代码的生命周期里维护的时间远大于编写的时间。代码写出来可能只需要一小时但这个文件可能要在项目里存活好几年期间会有不同的人来读它、改它、调试它。如果每个人的风格都不一样每次切换上下文都需要额外花时间去破译对方的习惯。PEP 8的价值就在于消除了这种认知负担让读者把全部注意力放在逻辑本身而不是猜测某个变量名是不是拼写错了。另一个现实原因是很多团队都在用自动化工具检查代码风格。GitHub上有大量开源项目会配置CI持续集成代码推送上去后自动跑一遍风格检查不合规的直接拒绝合并。你要是打算参与开源项目PEP 8就是最基本的入场券。1.3 谁在要求你遵守PEP 8除了团队规范和开源项目之外Python自带的标准库本身就是PEP 8的最佳示范。你随便打开一个标准库模块比如os、collections里面所有代码都严格遵循PEP 8的约定。这也意味着如果你不遵守PEP 8读标准库源码时就会感到别扭反过来当你养成了PEP 8的书写习惯读任何高质量Python代码都会非常流畅。各类代码托管平台也在推动这个规范。比如GitHub的代码审查功能很多团队会在PRPull Request阶段用工具自动标记风格问题这些问题虽然不影响功能合并但会显示为Checks failed给提代码的人不少压力。与其被工具提醒返工不如从一开始就养成好习惯。2. 新手最容易踩的四个坑缩进、行长、空行和命名2.1 缩进4个空格Tab是万恶之源但也未必PEP 8明确规定每一级缩进使用4个空格不要使用Tab。这一点是新手最容易忽视的因为默认情况下很多编辑器按Tab键会插入一个Tab字符而不是4个空格。如果你同事用空格缩进你用Tab缩进虽然肉眼看着差不多但Python解释器会直接报IndentationError。打个比方空格和Tab混用就像你用中文交流对方用英文回复虽然都是对话但彼此根本听不懂。更头疼的是这种错误通常不是一开头的缩进而是藏在某个函数体中间排查起来非常费劲。我之前见过一个新手代码跑了20多行才报缩进错误就是因为前面恰好每一行的空白看起来都一样长实际上混着Tab和空格。现在的编辑器基本都能自动处理这个问题。VSCode里可以设置editor.insertSpaces: trueSublime Text在右下角可以切换Indent Using SpacesPyCharm默认就是4个空格。你只需要确认一下自己的编辑器设置以后编辑的时候不要手动按Tab键。但我要补充一句在某些极端场景下Tab其实也有人在用比如你想节省文件体积。但Python社区的主流共识非常明确——4个空格没有例外。哪怕是多层嵌套的复杂代码也要坚持4空格一级最多通过合理的结构设计来减少嵌套层级。顺便说一句如果你发现自己需要七八层缩进才能写完一个函数那大概率是代码结构出了问题应该想办法拆分。2.2 行长79字符的约定是从打印机时代传下来的PEP 8建议每行代码最长79个字符对于文档字符串和注释限制是72个字符。这个数字很多人觉得莫名其妙其实它的历史可以追溯到上世纪70年代的终端设备当时的终端宽度通常是80列79是为了留一个字符的余量防止换行时折页。虽然现在的高分屏完全能显示更长的行但这个约定一直延续了下来。你可能觉得79字符太短了随便写个表达式就超了。但仔细想想过长的行本身就是一种坏味道——要么是逻辑太复杂要么是命名太啰嗦。如果一行代码需要左右滚动才能看完读者很难一眼掌握它的结构。PEP 8给出了规范的续行方式在括号内换行并且用挂行缩进hanging indent对齐。# 推荐的写法在括号内换行对齐到第一个元素 total (price_per_unit * quantity shipping_fee - discount_amount) # 不推荐的写法一行到底读起来费劲 total price_per_unit * quantity shipping_fee - discount_amount对于初学者我建议你记住一个更宽松但实用的替代方案很多团队把行长调整为100或120字符比如Google的Python风格指南就是120。如果你一个人写项目可以按79来练基本功但如果你加入了某个团队就按照团队配置来工具会帮你自动处理。关键是别让行长成为你不读PEP 8的借口——你可以放宽数值但要养成控制行长、合理换行的意识。2.3 空行分层的视觉语言PEP 8对空行的要求很简单函数和类之间用两个空行分隔类内部的方法之间用一个空行函数内部可以根据逻辑用空行分组。这个规则看起来不起眼事实上对代码的可读性影响极大。我见过不少新手喜欢把空行删得干干净净觉得这样代码紧凑。实际读起来一大块没有呼吸感的代码阅读体验非常糟糕尤其当你想快速找到函数定义位置的时候眼睛得花不少功夫。反过来如果每个函数之间都有两个空行你扫一眼就能看到代码的骨架结构。函数内部的空行也有讲究。比如一个函数可能包含参数校验核心计算结果格式化三个逻辑块每个块之间加一个空行读代码的人就能更快地理解流程。注意这不是硬性要求但它是很好的表达习惯。def process_order(order_id): 处理订单校验、计算、更新状态。 # 校验订单是否存在 order get_order(order_id) if order is None: raise ValueError(fOrder {order_id} not found) # 计算金额并更新数据库 total calculate_total(order) update_order_total(order_id, total) # 记录日志并返回结果 logger.info(Order %s processed, total%s, order_id, total) return total2.4 命名读代码的人不需要猜谜命名是PEP 8里篇幅最长、也最容易被初学者忽略的部分。核心规则其实不多类名用CapWords驼峰式比如class ShoppingCart函数名和变量名用小写加下划线snake_case比如get_user_name()max_retry_count常量用全大写加下划线比如MAX_CONNECTIONS 10私有变量或方法用一个下划线前缀比如self._internal_cache尽量避免用双下划线开头的名字除非你要做name mangling否则容易让人困惑命名这件事最核心的原则不是语法而是准确和一致。变量名要能够自解释看到total_price你就知道它是什么看到t你就得猜。有些新手喜欢用拼音缩写比如sl代表数量这在纯中文团队内部还能沟通一旦代码交给别的人维护几乎是灾难。我建议初学者养成一个习惯写完一段代码后回过来看看变量名问自己如果一个月后我看到这个名字能不能马上想起它是干什么的。如果答案是否定的就换个更具体的名字。函数名通常是动词或动词名词的组合能一眼看出函数做了什么布尔变量名最好用is_、has_、can_开头让条件判断读起来像自然语言。# 糟糕的命名 def f(x): r [] for i in x: if i % 2 0: r.append(i) return r # 清晰的命名 def get_even_numbers(numbers): even_numbers [] for number in numbers: if number % 2 0: even_numbers.append(number) return even_numbers3. 容易被忽略但时刻在影响代码气质的细节3.1 导入语句不是随便放哪都行PEP 8对import的规范其实很多程序员都不了解但它在实际协作中特别重要。规则有三条所有import语句必须放在文件顶部位于模块文档字符串之后、其他代码之前每个import导入一个模块虽然from x import y, z是允许的导入顺序分三组组间用空行隔开标准库、第三方库、本地库这种排序不是为了好看而是为了快速回答这段代码依赖了哪些外部库这个问题。当你拿到一个新项目先看文件顶部的import列表就能判断项目引入了哪些第三方依赖、有没有本地模块间的耦合。很多IDE也依赖这个顺序做自动导入的功能。# 推荐分组清晰 import os import sys import requests import yaml from myproject.utils import format_date有一个细节我特别想提醒新手尽量避免from module import *这种写法。它会污染当前命名空间让变量来源变得不清晰而且会触发很多静态检查工具的警告。如果你的代码里出现了*导入别人读的时候根本不知道这个名字是哪里来的调试起来会非常痛苦。3.2 空格的使用习惯运算符两边别挤成一团PEP 8对空格的规则非常细致核心精神是用空格让代码的结构一目了然但不要让空格本身变成噪音。需要加空格的位置包括赋值运算符两边比如x 10而不是x10比较运算符和逻辑运算符两边比如if a b and c ! d:二元算术运算符两边比如price * quantity逗号、冒号、分号后面要加一个空格除了行尾的注释前可以灵活不需要加空格的情况包括函数调用时括号内部不要有空格比如foo(1, 2)而不是foo( 1, 2 )索引和切片的时候方括号内部不要有空格比如list[0]参数默认值时等号两边不加空格比如def foo(argNone):。说一个最常见的错误很多人在写函数定义时会在参数默认值两边加空格写成def foo(arg None)PEP 8明确不推荐这种写法它建议def foo(argNone)。原因很微妙是赋值但参数默认值在语义上是函数的一部分跟普通赋值不太一样不加空格可以让它跟函数签名融为一体。# 推荐 def connect(host, port8080, timeout30): ... # 不推荐 def connect(host, port 8080, timeout 30): ...这里补充一个原则所有空格规则的核心目的是让代码没有冗余。一个空格能区分就不需要两个没有空格能表达就不加。3.3 注释和文档字符串好注释解释为什么而不是是什么PEP 8要求注释是完整的句子并且与代码保持同步更新。但对于初学者来说最重要的还是理解注释到底该写什么、不写什么。很多人喜欢写这种注释# 将x加1 x x 1这种注释完全是在浪费读者的时间。代码本身已经说了它在做什么注释应该解释的是为什么要这样做或者为什么不用别的方式。比如# 用循环而不是列表推导式因为这里需要对每个元素做日志记录 result [] for item in items: result.append(transform(item)) log.debug(transformed: %s, item)注释是给未来的维护者包括三个月后的自己看的。我在实际工程中见过太多注释和代码矛盾的情况——代码改了很多次但注释还停留在第一版误导后来的人。所以写注释最重要的一条原则是要么写清楚为什么要么别写。文档字符串docstring是Python里特有的东西它是模块、函数、类和方法的第一个字符串用来描述它们的用途。PEP 8没有规定必须用哪种风格只要求所有公共模块、函数、类和方法都要有docstring。一般来说用三引号包裹第一行是简短的说明然后空一行再写详细描述。def calculate_discount(price, percent): 计算折后价格。 参数: price: 原价整数或浮点数 percent: 折扣百分比比如 0.1 表示打9折 返回: 折后价格 return price * (1 - percent)4. 别再手动排版了让工具替你守规矩4.1 flake8你的代码体检医生手动对照PEP 8规则记细节很痛苦好在Python生态里有成熟的自动化工具。我首推flake8它是一个把风格检查pycodestyle、逻辑检查pyflakes和复杂度检查mccabe合在一起的工具。安装和维护都很简单pip install flake8 flake8 your_script.py运行之后它会输出类似这样的结果your_script.py:10:5: E128 continuation line under-indented for visual indent。看起来有点吓人其实格式很固定文件路径:行号:列号: 错误代码 描述。错误代码第一个字母代表检查类型E是风格错误W是警告F是逻辑错误C是复杂度问题。我之前在一个开源项目里第一次跑flake8发现十几个W605无效的转义字符。这属于跨平台兼容性问题Windows路径字符串里的\t会被当成Tab转义用原始字符串r...就能解决。这些都是新手容易踩的坑工具能帮你提前暴露。4.2 black无情的格式化利剑如果说flake8是体检医生那black就是强制执行手术。它号称uncompromising code formatter不妥协的代码格式化器你给它任何符合语法的Python代码它都会自动重排成统一的PEP 8风格不需要你做什么选择。pip install black black your_script.pyblack的核心设计哲学是少即是多它故意不提供太多配置选项目的是让大家停止争论格式把精力放在逻辑上。这在团队协作里非常有用。以前开代码审查会经常有人为了这里该不该加个空行争论半天引入black之后所有格式问题都由机器决定人只讨论真正重要的逻辑问题。经过black格式化的代码会特别容易被各类工具识别所以现在很多开源项目把black作为强制环节。不过要注意black和flake8偶尔会有冲突。比如black默认的行长是88字符而flake8默认检查79字符。解决方案很简单在flake8配置里把max-line-length改成88或者在black里设成79。这类问题属于工具之间的磨合每个项目解决一次就行网上搜一下就是现成答案。4.3 isort导入排序小管家前面说过导入顺序的规则手动维护非常烦人尤其是当文件越来越大、导入越来越多的时候。isort就是专门干这个的工具它自动把导入语句分成标准库、第三方库、本地库三组每组内按字母序排列还能帮你处理from x import y的排序。pip install isort isort your_script.py最爽的是isort可以跟black配合使用。有一个注意事项isort默认会强制所有from导入在一行以内但black会把它格式化成多行。好在isort提供了配置项让它把force_single_line设为False或者直接使用isort --profile black这样两个工具就不会打架。4.4 pre-commit钩子把检查门禁装到写代码之前工具装好了但如果每次都要手动跑一遍很快就会偷懒不做了。真正的工程化做法是利用pre-commit框架在每次git commit提交代码之前自动运行检查和格式化发现问题就拦下来让你改完再提交。安装方式pip install pre-commit然后在项目根目录建一个.pre-commit-config.yaml文件repos: - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort args: [--profile, black]之后运行pre-commit install框架就会注册到Git的钩子里。每次你git commit它都会先跑一遍这些工具有修改就自动改改完你需要重新暂存再提交。这个过程我刚用的时候也很不习惯但坚持两周之后就离不开了——因为代码质量在这些工具的强制下始终保持在一个很稳定的水平线上。5. 规则要守但也要知道什么时候可以变通5.1 一致性永远优先PEP 8在开头就写了一句话一个项目里的一致性比这个项目跟PEP 8的一致性更重要。很多初学者把PEP 8当成铁律每一条都要严格遵守甚至不惜为了满足规范把代码写得别扭。但实际上当你加入一个已有项目的时候最优先考虑的是这个项目内部已经形成的风格如果项目里统一用100字符行长、用不用docstring的习惯等等你应该跟随这个项目的节奏而不是强行把整份代码改造成教科书式的PEP 8。比如有的老项目还保留着Tab缩进的风格你作为新成员正确的做法是先跟随现状。如果要统一风格应该和团队讨论后通过一次专门的提交完成而不是在自己的功能提交里悄悄改了全部文件——这会让代码审查的diff变得巨大谁也不知道你改了谁、动了什么。5.2 几条真实的破例场景PEP 8允许在特定情况下适当变通我举几个我在实际项目中遇到过的例子一行很长的URL或路径字符串确实不适合切开可以在代码里维持一行用# noqa注释告诉flake8忽略这一行的检查。noqa是no quality assurance的缩写不过它只影响那一行。某些非常快速的临时调试脚本如果你明确知道这个脚本只会用一次是不用死磕风格的。前提是写完就删不要留在项目里。大型数据表格里对齐等号两边的值可以让代码更整齐属于合理的为了让可读性更好而打破规则。# 垂直对齐可读性可能是更好的 config { host : localhost, port : 8080, max_connections: 100, }但注意这类特例意味着你已经理解了规则并且有能力判断规则的边界。如果还没完全掌握PEP 8我建议先老老实实按规范来等你写了足够多的代码自然能分清楚哪些地方可以灵活。5.3 别让风格问题阻碍你写代码最后我想说一点轻松的。很多新手看完PEP 8的内容之后会变得束手束脚——我写的每一行是不是都不规范我的命名是不是不够好甚至因为担心格式问题而迟迟不动手写代码。这其实完全没必要。风格规范是加分项不是必要项代码能跑、逻辑清晰、功能正确才是最核心的。我建议的学习路径是这样的第一阶段不管格式先写出来第二阶段每写完一个文件用black格式化一遍用flake8检查一遍看看被改了什么第三阶段逐渐养成按规范书写的习惯让好风格变成肌肉记忆。这个过程通常需要一两周的时间一旦度过这个阶段以后再回头看自己早期的代码会有非常明显的舒适感差异。用我的话说PEP 8就像写字练字里的字帖。你不需要在每次写字前都背一遍字帖规则但经过一段时间的临摹和内化你平时随便写出来的字就已经是工整的了。代码风格也是一样它不是目的而是通往整齐、清晰、好协作的代码世界的一条路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进