ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PEP 8 Python编码规范指南:从缩进到自动化工具实践

PEP 8 Python编码规范指南:从缩进到自动化工具实践 1. 为什么每个 Python 新手都应该认真对待 PEP 8说实话我刚学 Python 那会儿对 PEP 8 是有点不以为然的。那时候觉得代码能跑就行缩进、空格、换行这些细枝末节纯属浪费时间。直到后来在一个多人协作的项目里被代码 Review 打回来三次全是因为格式问题才老老实实把 PEP 8 翻出来读了一遍。现在回头看那次“被教育”的经历反而是我写代码习惯的分水岭。PEP 8 是 Python Enhancement Proposal 8 的缩写翻译过来就是 Python 增强提案第 8 号。它不是什么强制标准而是一份官方推荐的代码风格指南。Python 之父 Guido van Rossum 和其他核心开发者共同编写了这份文档目的很简单让所有 Python 代码看起来像同一个人写的。为什么这很重要因为代码首先是给人看的其次才是给机器跑的。机器只需要语法正确就能执行但人需要结构清晰才能理解。一个项目从几个人发展到几十个人代码的阅读次数远远多于编写次数。统一的风格规范能让你和别人都少受点罪。这份指南适合谁如果你刚开始学 Python或者已经写了一段时间但没注意过代码格式PEP 8 值得你花一个小时通读一遍。不需要背下每一条规则先记住最常用的那些然后让工具帮你检查剩下的。2. 核心规范拆解从缩进到命名一条条讲清楚2.1 缩进Python 的命根子Python 用缩进来表示代码块这一点和 C、Java 这类用花括号的语言完全不同。缩进一旦乱掉程序直接报错连编译都过不去。PEP 8 规定使用 4 个空格作为一级缩进不要用 Tab。很多新手会问Tab 键按一下就缩进了不是更方便吗问题在于Tab 在不同编辑器里的显示宽度不一样可能在一个编辑器里是 4 个空格在另一个编辑器里是 8 个空格。同一段代码在不同环境下显示的效果完全不同极易引起混乱。你可以把代码缩进理解为文章的分段——段落清晰的文章读起来舒服而缩进统一的代码看起来也是同样的效果。Python 社区有个不成文的共识永远不要在 Python 代码里混用 Tab 和空格。我自己就踩过这个坑看起来是正常的缩进一运行就报 IndentationError排查了大半天才发现是某个文件里混进去了一个 Tab。如果缩进行需要换行比如函数参数太长拆成多行操作方式又不一样了第一种方式是延续缩进换行后的内容继续对齐到上一行的起始括号位置第二种方式使用悬挂缩进第一行没有参数后续参数整体缩进一级通常缩进 4 个空格和函数体区分开。2.2 每行代码的最大长度79 还是 88PEP 8 原文建议每行代码不超过 79 个字符理由是早期终端宽度一般是 80 列留一个字符给换行符。到了 2024 年的今天这个数字放到大屏幕上其实早就不那么必要了但它在某种程度上依然有讨论价值——即使屏幕再宽过长的代码行依然会让人在阅读时感到吃力。实际操作中很多团队已经把这个限制放宽到了 88 或者 100 字符像 Black 格式化工具默认的就是 88。如果是在自己维护的小项目里放宽到 100 也完全没问题。关键是和团队保持一致不要你定 79他定 100最后代码里两套标准并存。我刚工作那会儿单行代码动不动就写一百多字符像什么result [transform_func(item) for item in some_long_named_list if condition_check(item) expected_value]一行装下五六个模块。后来不记得哪天自己读回旧代码一眼扫过去根本不知道这行在干什么——这行里塞了太多逻辑让阅读体验变得特别糟糕。从可读性角度来看行长的意义其实不是“每行长短”而是“每行做事的多少”。一行代码塞进太多逻辑哪怕行长没超标也是一个坏味道。2.3 空行怎么用层级感靠它PEP 8 对空行的规定很简洁顶层函数和类的定义之间空两行类内部的函数定义之间空一行一组有紧密关系的代码之间可以加一个空行辅助区分。不要小看空行它的作用相当于文章里的分段。一堆函数密密麻麻粘在一起眼睛很难定位到目标位置空出两行之后整个文件的结构一下变清晰了。还有一个很容易被忽略的细节文件末尾要以一个换行符结束。很多编辑器现在默认会这么做但有些老配置可能会漏掉。文件末尾缺换行符在 diff 工具里会显示一行奇怪的提示还会在某些构建工具里引发不必要的警告。2.4 空格的使用运算符与逗号有一次我给学生批改作业看到有人的代码写成了x10 if x5 else 5我当时在批注里写了一句“PEP 8 会哭的”。这个例子生动地说明了一个问题运算符两侧的空格看似极小的细节却是最容易暴露一个人有没有读过 PEP 8 的地方。规则并不复杂二元运算符赋值号、加号、减号、乘号、比较号等两侧各加一个空格逗号、分号、冒号后面加一个空格前面不加函数调用时参数列表内部不加多余空格比如f(1, 2)而不是f( 1, 2 )取下标或切片时方括号内部两侧不加空格比如lst[0]而不是lst[ 0 ]函数名和参数列表之间不加空格比如func(x)而不是func (x)关键字参数、默认参数赋值号两侧不加空格比如def func(x1)而不是def func(x 1)。重点说一下这个“赋值号”的差别普通赋值x 1两边有空格而关键字参数def func(x1)的等号两边则不加空格这是很多人会犯错的地方。规则的内在逻辑是x 1是一个执行动作而x1是在“声明参数”两者在语义上有区别所以视觉上也应当区分。还有一个很多人忽略的细节默认参数里写类型注解的时候x: int 1中间要有空格。如果直接把注解写法漏掉了整个签名看起来会特别挤。不用刻意记交给格式化工具处理即可。2.5 命名规范好名字减少一半注释PEP 8 对命名的建议最有价值的部分其实是“体系化”这三个字——它不是规定你必须用哪种风格而是告诉你不同风格的标识符在代码里扮演的角色是完全不同的。我整理了一个新手最容易用到的对照表变量名、函数名全部小写多个单词用下划线连接比如user_name、get_total_price常量全部大写用下划线连接比如MAX_RETRY_COUNT。Python 没有真正的常量机制全靠命名约定提醒“别改它”类名使用大驼峰比如UserProfile、OrderManager模块名和包名小写尽量简短比如tools、db_utils私有变量或方法前缀一个下划线比如_helper()、“_private_cache”这只是约定不阻止访问两个下划线开头的名字会触发 Python 的名称改写机制用于避免在继承中意外覆盖这种情况比前置单下划线少得多避免使用会和内置函数撞车的名字比如给变量命名为list、dict、str、type这属于给自己挖坑。值得展开讲一下“下划线前缀”的细节。很多人第一次看到_name的时候会误以为它是“不能访问”的意思实际上 Python 并没有强制保护。它的真实含义是这个属性或方法属于内部实现细节外部代码不应依赖它。写代码的人要自觉遵守不要主动去调用别人带下划线的成员。命名这件事我的经验是一个名字如果让你想超过 10 秒钟那大概率还没想清楚这个东西到底是做什么的。与其仓促定一个模棱两可的名字不如停下来重新抽象一下。2.6 import 的排列顺序与位置PEP 8 要求 import 放在文件顶部并且遵循以下顺序标准库模块第三方库模块本地应用 / 库的特定模块。每组之间用一个空行隔开。同一组内的 import 建议按字母顺序排列。这个要求的核心逻辑是依赖关系的“可视化”——从上到下你一眼能看出哪些是 Python 自带的哪些是外部安装的哪些是自己项目里的。你在排查“某某模块怎么装不上”的时候这个顺序能帮你快速定位问题的源头。还有一个细节说一下推荐使用import os/import sys这种写法而不是from os import *。星号导入会把一个模块里所有不以下划线开头的名字全部拉进当前命名空间你会失去对名字来源的控制还会污染命名空间在命名冲突时非常难排查。PEP 8 明确建议不要使用通配符导入。2.7 注释与文档字符串写给下一个接手的人PEP 8 对注释的指导原则可以浓缩成一句话注释应该解释为什么而不是是什么。代码本身已经说明了它做了什么注释的作用是补充代码之外的背景信息。我见过有人给每一行代码写注释读起来完全是在复读语义没有任何信息增量水平高低立见。写注释的基本规范每行注释不超过 72 个字符注释要跟着代码缩进不能随意顶格优先使用整行的块注释而不是行尾注释行尾注释和代码之间至少间隔两个空格。文档字符串docstring是另一层东西。它写在模块、函数、类的开头用三个双引号包裹作用是解释“这段东西是干嘛的、怎么用”。PEP 8 对文档字符串的要求相对宽松所有公开的模块、函数、类、方法都应该有文档字符串。我个人体会一个省力的习惯是函数名写出来后先不写函数体而是先把文档字符串写出来描述它要做什么、参数是什么、返回值是什么。如果文档字符串写不顺畅说明这个函数的设计还没想清楚。想清楚了写实现只是水到渠成的事。3. 几个特别值得注意的进阶场景3.1 比较与判断如何写条件表达式条件表达式的风格也属于 PEP 8 讨论的范畴不过它往往容易被许多人忽略。None的判断建议使用is None而不是 None真值判断可以直接写成if not lst:不需要写if len(lst) 0:。需要特别强调的是对单个变量做真值判断的写法if x:内部包含了更深的考虑比较的是值的大小而is比较的是身份也就是内存地址。None在 Python 里是一个单例对象用身份判断来确认“这个变量是不是 None”比依赖它的值更可靠、更快。对序列做判断时if list:判断非空直接依赖列表的布尔真值即“有元素就为真的规则”。这套逻辑在平均性能表现上不输显式判断写法代码的表达力更强写起来也自然。不要写if list []:这种写法既别扭又没有意义。3.2 复合语句一行里塞多个语句是反模式PEP 8 强烈建议不要在一行里放多个语句比如不要写if foo blah: do_blah()这种。为什么不行因为这样做会牺牲可读性。代码的分层结构是表达逻辑的重要手段当你把条件和执行压在同一行里目光会失去焦点。等以后要在条件分支里加更多动作的时候又得把代码拆开重排完全没有必要。当然有一种场景是可以接受的三元表达式。比如x 1 if flag else 0。这是 Python 语法本身支持的分支表达式和“在一行里塞多个语句”性质完全不同。但要注意如果三元表达式里嵌套三元表达式代码基本没法读宁可拆成几步。4. 工具链推荐让机器替你查风格4.1 第一个要用的flake8flake8 是一个综合检查工具它把 pyflakes 和 pycodestyle 整合在了一起能同时检查风格问题空格、空行、行长等和一些简单的逻辑问题定义了没用的变量、import 了没用到的模块。安装它只需要一行命令pip install flake8然后运行flake8 your_code.py它会输出类似这样的报告your_code.py:10:5: E303 too many blank lines (2) your_code.py:25:13: W291 trailing whitespace your_code.py:30:1: F401 os imported but unused这里E303是格式错误代码W291是警告F401是 pyflakes 的检查结果。它给出的信息包括文件路径、行号、列号、问题代码和解释你照着改就行。4.2 让代码自动格式化BlackBlack 是一个非常强势的格式化工具。它和 flake8 的组合拳逻辑很有意思——Black 一出手就是先斩后奏完全无视你原本的排版按固定风格重新生成。很多人刚接触它的时候会觉得太野蛮了但这正是它的优点不给你留纠结的余地。安装pip install black格式化文件black your_code.pyBlack 的风格和 PEP 8 大体一致但在一些细节上做了现代化调整比如默认行长是 88 而不是 79。它还特别擅长处理那些“长度超了怎么换行”的问题——它有一套自己的算法来决定怎么拆行拆出来的结果基本不用再人工调整。反过来说如果你对排版有强烈的个人偏好用 Black 会很痛苦因为它不会跟你商量。4.3 排序 import 的专用工具isortisort 是专门处理 import 排序的工具能自动把 import 理清顺序分好标准库、第三方库、本地模块三个区段。pip install isort运行isort your_code.py如果你配合编辑器一起使用是可以在保存文件时自动触发这些工具直接在写代码的过程中完成格式修正。VSCode 里可以配置format on savePyCharm 也有“在保存时运行外部工具”的插件机制。我实际工作流里的执行顺序是这样的先让 isort 处理 import再用 Black 格式化代码最后跑一遍 flake8 做终检。两个自动工具的区段刚好互补——Black 不长于处理 import 排序isort 则专门管这一块最终通过 flake8 来兜底确保两边配合完没有遗漏。4.4 编辑器配置VSCode 实操在 VSCode 里你需要先安装 Python 扩展。然后在 settings.json 里加入这一段{ python.linting.flake8Enabled: true, python.linting.enabled: true, python.formatting.provider: black, [python]: { editor.formatOnSave: true, editor.codeActionsOnSave: { source.organizeImports: true } } }保存文件的时候黑盒自动帮你排版好import 也自动排好序flake8 的波浪线提示也直接画在代码里。这几样工具配合起来基本实现了“写的时候不用管格式存盘后格式自动成型”的效果。但有一点必须提醒这些工具不是万能的。它们能解决格式问题但解决不了命名是否清晰的问题更解决不了逻辑是否合理的问题。PEP 8 的最终目的是让代码更好读而不是让代码过检查工具。如果一段代码格式上完全符合规范但变量名叫a、b、c函数体五百行那它依然是难维护的代码这也就是为什么会有“编码规范背后是编码思维”这一说。5. 常见问题与排查技巧实录5.1 不完整的问题速查表以下是我在实际带项目过程中经常遇到的一些风格问题整理成一个速查表问题错误示例正确示例说明缩进用 Tabif x:下面按 Tab4 个空格混用会报 IndentationError运算符没空格x1/ab*cx 1/a b * c变量赋值、二元运算符两侧加空格默认参数等号前后加空格def f(x 1)def f(x1)声明参数时等号两侧不留空格逗号后面没空格f(1,2)f(1, 2)用逗号分隔单元素加空格函数名和括号之间加空格f (x)f(x)括号紧随函数名导入了没用import os但其实没用到删掉用 flake8 的 F401 检查行尾有空格x 1x 1肉眼难发现用 flake8 或编辑器高亮文件名撞模块名string.pymy_string_utils.py会和标准库冲突5.2 新手最容易踩的 5 个坑混用 Tab 和空格我从网上复制了一段代码粘贴到自己的文件里运行直接报IndentationError: unexpected indent。原因是原文件用的是 Tab。解决办法是在编辑器里打开“显示空白字符”功能改完再全选转换成空格。给变量起名为list在函数里写了个list [1, 2, 3]后面再想用list()构造新列表直接报TypeError: list object is not callable。这个报错很迷惑排查半天才发现是变量把内置函数覆盖了。看到警告不当回事flake8 报 W291 尾随空格你觉得无所谓用鼠标点掉。结果文件合并到项目里其他人的 pre-commit 钩子直接报错CI 红了一大片最后不得不回退。警告就是提醒别欠债。注释和代码不一致写了一行注释后来代码逻辑改了注释没改第二天自己读代码都看懵了。注释要保持和代码同步改代码时必须检查注释。非要手动排版花了大量时间手动调空格对齐非常费劲。同样的效果用自动化工具一秒能解决。我见过有人在一个 200 行的文件上手动调格式调了一个小时他当时完全忘记了自己电脑里已经装好了 Black 这个工具。5.3 一个实地排查案例有一次我在同事的 PR 里看到一个问题文件顶部import顺序乱排第三方库和标准库混在了一起。代码跑起来当然没问题可是在 Review 里视觉上非常跳脚。usort 自动跑了一遍文件顶部直接清清爽爽分了三段。其实很多风格问题用自动工具就能解决百分之八十没必要在 Review 环节浪费人的注意力。另一个真实的案例是一个数据处理的脚本函数名叫做compute()。这个函数既算了平均值又做了数据的去重还写了个 Excel 报告三百多行。所有变量全是单字母也没有空行分隔逻辑块。这代码风格算是“没有风格”读完等于看天书。后来我把它拆成了compute_average()、remove_duplicates()、export_to_excel()三个函数——命名一变结构一拆函数要做什么立刻清楚了。这也是我在工作里最深刻的体会PEP 8 能改的是代码的皮相命名和拆分是否合理才是代码的骨相。6. 写 Python 代码时更好的排序习惯与团队约定开发一个开源项目、在公司团队协作PEP 8 只是一个起点。每个团队都该有一套自己的“扩展版 PEP 8”用来回答官方规范没有覆盖的问题。比如这些是使用单引号还是双引号PEP 8 没规定但 Black 默认使用双引号。建议团队统一是把模块控制在 200 行还是 500 行内官方没有要求但为了可维护性会有一个默认倾向什么样的函数算太长有人设定 80 行有人觉得 30 行就该拆。拆分的标准是“函数体是否只做一件事”类里面的方法顺序怎样排是先写__init__再写公开方法最后写私有方法还是直接按字母排团队统一即可。这些约定建议写成一份简短的团队文档存到仓库根目录下的CONTRIBUTING.md或者直接放在 README 里。新人接手项目时只需读一份文档不用靠蒙。还有一个好用的机制pre-commit。这是把 flake8、Black、isort 等工具直接挂到 git 提交之前的钩子。每次提交代码都会自动检查不通过就阻止提交。强制力比编辑器配置高得多。pre-commit 配置一个极简的例子repos: - repo: https://github.com/psf/black rev: 24.2.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 rev: 7.0.0 hooks: - id: flake8 - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: - id: isort args: [--profile, black]提到 pre-commit 文件有一点必须提醒所有工具版本必须锁定不能写latest。因为不同版本的 Black 格式化结果可能不同如果两个人用的版本不一样diff 会比较乱看得人非常头疼。锁版本是为了保证“同一段代码在任何人的机器上格式化结果完全一致”。写完配置后运行一次pre-commit install钩子就生效了。从此以后你提交代码时工具会自动检查你的代码文件发现问题会直接弹出来。如果你用了自动修复选项Black 和 isort 还直接帮你把文件改好你只需要重新 git add 就能提交。7. 从 PEP 8 到更广阔的 Python 编码思维学 PEP 8 不是目的写出易读、易维护的代码才是目的。风格规范真正的作用是在你这个人的品味和团队之间建立一道共识的防火墙减少无意义的争论和精力损耗。格式这件事不该由争论来解决直接统一用工具就能锁死。跳过 PEP 8 直接学习 Python 语法短期内确实没有明显的障碍。但一旦你需要阅读别人写的代码或者别人来读你写的代码你就会立刻体会到什么是“代码即沟通”。一个整洁清晰、格式统一、命名有逻辑的文件和一个随手写完从来没有整理过的文件给阅读者带来的认知负担有巨大差异。你花了大力气整理思路把代码搭成能跑的状态为什么不再花一分钟把它变整齐让所有人都能流畅地读懂呢我现在看回自己三年前写的代码风格和现在有很大不同。这说明 PEP 8 本身也在随着时代演进不同工具的默认值也在变化。你不必一次记住所有规范细节记住核心原则剩下的交给工具链——打开编辑器保存文件时自动格式化配合 flake8 做检查。随着代码越写越多你会逐渐内化这些规则到最后根本不用刻意去想手写出来的代码就自带 PEP 8 的味道。我自己带过的每一个项目第一批配置里永远有 Black、isort、flake8 这三件套。等新同事入职第一天教他跑通环境第二天就会看到他已经能在 PR 里交干净漂亮的代码了。工具能帮你解决的问题就别消耗人的意志力了。最后分享一个我自己坚持了很多年的小习惯每次新建 Python 文件的第一个动作是把文档字符串和函数签名先写好然后交给 Black 格式化成标准形状再开始填函数体。这个动作会提醒我这段代码写出来是为了让谁看以及我希望它表达什么。分享给大家可以试试看也许能给你带来一个新的视角。
返回列表