AI 技术博客
返回首页
Python 基础 · 14 分钟阅读

PEP 8 代码规范:写出可读的 Python

## 引言 这是第一章的第 7 篇。上一篇我们练熟了 REPL,能在命令行里快速验证任意想法,工具链用起来越来越顺。但从第 2 篇开始,你写的每一行代码都会沉淀进 `.py` 文件里,被你自己、同学、同事反复阅读和修改——"能运行"只是及格线,"容易读"才是分水岭。 本文解决的核心问题是:**Python 代码的"正确写法"由谁说了算,以及怎样不靠意志力地执行它**。答案的前半是 PEP 8 这份官方风格指南,后半是 pycodestyle、flake8、black 这类自动化工具。读完本文,你将能写出风格规范的代码,并把"检查风格"这件事交给机器——这比任何"规范背诵"都可靠。本文的代码示例建议在你在前文创建的虚拟环境或全局环境中依次运行。 ## PEP 是什么:Python 社区的正规军机制 先认识 PEP 这个词。**PEP**(Python Enhancement Proposal,Python 增强提案)是 Python 社区提出、讨论并定型技术方案的正式流程,由 PEP 1 号文档定义。每一项重要决策——新语法、新标准库模块、治理规则——都先以 PEP 草案形式公开,经讨论修订后被官方接受或拒绝。Python 之所以能保持二十多年风格统一,这套"先提案、后评审、再落地"的流程功不可没。 PEP 8 是其中最特殊的一份:它不增加任何功能,只规定"代码应该长什么样"。它的正式全称是 *Style Guide for Python Code*,2001 年由创始人 Guido van Rossum 与 Barry Warsaw 撰写,后由 Nick Coghlan 等维护,历经多次小修沿用至今。为什么 Python 需要这种"毫无功能"的文档?因为 Python 的设计哲学里,"可读性"是硬指标——Guido 本人有句被社区反复引用的话:代码被阅读的次数远多于被编写的次数(code is read much more often than it is written)。读代码的"人"每次切换风格都需要重新适应,统一风格就是降低全社区的认知成本。 PEP 8 的权威地位意味着:**不遵守 PEP 8,Python 也能跑**;但遵守它,是 Python 社区对"合格代码"的默认共识。它是规范(convention),不是语法(syntax)——语法错误是硬错误,风格问题则靠工具与约定约束。这也是为什么它总与"检查工具"成对出现。 ## 核心规则一:缩进、行长与空行 PEP 8 的第一大类规则是**代码布局**(code layout),管的是"骨架"。 **缩进**:统一用 4 个空格,禁止 Tab 与空格混用。Python 语法本身根本不允许混用——混用时解释器直接抛 `TabError: inconsistent use of tabs and spaces in indentation`。下面的代码块会真实触发这个错误: ```python def add(a, b): if a > 0: # 这一行用 1 个 Tab 缩进 return a + b return b ``` 在 Python 3 里,`Tab` 与空格的混用是被编译器主动拒绝的,这是为数不多的"风格问题同时是语法错误"的情况。为了避免这条错误,主流编辑器都内置"Tab 转空格"(例如 VS Code 右下角的缩进设置,第 9 篇会细讲)。 **行长**:PEP 8 建议每行不超过 **79** 个字符,注释与文档字符串不超过 72 个。79 这个数字源自早期终端 80 列的物理限制,保留一个字符容纳行尾换行。今天的宽屏显示器让 79 显得保守,于是社区出现两个常见替代:Django 等项目用 99,black 格式化工具默认用 88。行太长的标准化解法是**在括号内换行**(隐式续行),这与第 6 篇 REPL 里讲过的括号续行是同一机制: ```python total = (sum(scores) + len(scoreboard) * 2 - penalty) ``` **空行**:模块级函数与类定义之间空 **2 行**,类内部的方法之间空 **1 行**,函数内部按逻辑块酌情空 1 行。这条规则是初学者最容易忽略的,而工具能瞬间抓住它——后面你会看到 pycodestyle 的 E302 错误正是"expected 2 blank lines"。 ## 核心规则二:导入、空格与命名 **导入(import)**:PEP 8 要求每个 import 单独一行(`import os` 与 `import sys` 分行,但 `from os import path` 形式可以一行多个名字),并且按顺序分组:标准库、第三方库、本地模块,组间空一行。这是规则性的"电梯顺序"——保证任何一个文件里,导入区的信息流是稳定的: ```python import csv # 1. 标准库 import json import sys import requests # 2. 第三方库(前文 pip 安装过的) from myapp import config # 3. 本地模块 ``` **空格**:间隙性规则集中在"哪里不该有空格"。括号内侧不加空格:`spam(ham[1], {eggs: 2})`;逗号、分号、冒号后加一个空格:`if x == 4: print(x, y)`;二元运算符两侧各加一个空格:`x = a + b`;切片里冒号两侧不加:`record[1:4]`;为默认参数赋值时不加空格:`def f(a, b=1):`(这是最新版 PEP 8 与 black 的共同选择,早期版本曾建议加)。这些细节看似琐碎,但它们构成了"Python 长相"的底色。 **命名(coding style)**:PEP 8 的命名约定是三大件: - 变量、函数、方法、模块名:**snake_case**(小写加下划线),如 `parse_config`、`user_name`; - 类名:**CamelCase**(首字母大写的驼峰),如 `UserProfile`、`HTTPClient`; - 常量:**UPPER_CASE**(全大写加下划线),如 `MAX_RETRIES = 3`,通常写在模块顶层。 另有三个前缀约定:单下划线前缀 `_internal` 表示"类内部使用的弱私有成员"(约定而非强制,外部仍可访问);双下划线前缀 `__secret` 触发**名称改写**(name mangling),`__secret` 在类内部被自动改写成 `_ClassName__secret`,用于防被子类意外覆盖(第 8 章面向对象篇会展开);双下划线包围 `__init__` 这样的名字专属于魔法方法。此外,用单个尾随下划线避开关键字:需要给变量起名 `class` 时,写 `class_`。 两个经常被引用的"价值观"式规则:与 `None` 比较用 `is` 而非 `==`——`if x is None:` 或 `if x is not None:`,因为 `is` 比较的是身份,None 是单例,而 `==` 可能被对象的 `__eq__` 劫持;对布尔值不要写 `== True`,直接 `if flag:`。 ## 工具链:让规范检查成为流水线的一部分 人肉执行规范不可靠——总有忘记两空行、行长超标的时刻。工程化的答案是**把风格检查做成一条命令**。以下三个工具是当前的事实标准,先安装到前文创建的虚拟环境里: ```bash pip install pycodestyle flake8 black ``` **pycodestyle**(早期叫 `pep8`,2016 年改名)是 PEP 8 规则的直接检查器。给它一个文件,它逐行报告违规:行号、违规码、说明。我们来创建一个"风格反面教材",文件名 `bad_style.py`: ```python import sys import math def compute(a,b): total=a+b #没有空格的注释 return total class myClass: def method1(self): return 1 ``` 运行检查: ```bash pycodestyle bad_style.py ``` 真实输出如下(不同版本下违规行号可能略有出入,违规码是稳定的): ```text bad_style.py:4:1: E302 expected 2 blank lines, found 1 bad_style.py:4:14: E231 missing whitespace after ',' bad_style.py:5:10: E225 missing whitespace around operator bad_style.py:6:5: E265 block comment should start with '# ' bad_style.py:9:1: E302 expected 2 blank lines, found 1 ``` 每一条都对应 PEP 8 的具体条款:E302 缺两空行、E231 逗号后缺空格、E225 赋值号两侧缺空格(`total=a+b`)、E265 注释须以 `# `(井号加空格)开头。你可能注意到类名 `myClass` 没被报出来——因为**类名大小写规则不在 pycodestyle 的基础检查里**,它属于 flake8 生态的 pep8-naming 扩展(N801 类名应为驼峰),基础版默认不启用。这也说明:单一检查器覆盖不了全部规范,工具链要组合使用。 **black** 更激进:它是**代码格式化器**(formatter),直接改写你的文件,把风格统一到它自己的规范上(默认行长 88、双引号、括号内续行策略等),号称"不可争辩的格式化工具"。运行: ```bash black bad_style.py ``` 输出 `reformatted bad_style.py`(新版 black 还会附带一行"All done!"与改动统计),文件被原地改写。black 的价值在于**终结争论**:格式化结果由工具说了算,团队里只要约定"提交前跑 black",风格讨论就消失了。它的设计哲学是"零配置",唯一有争议的旋钮是行宽,用 `black --line-length 99` 可调。 **flake8** 则是"检查器全家桶":它内部组合了 pycodestyle(风格)+ pyflakes(静态错误,如未使用的导入 F401、未定义的名字 F821)+ mccabe(圈复杂度 C901),一条命令同时做三件事: ```bash flake8 bad_style.py --statistics ``` 输出除了违规清单,`--statistics` 还会汇总各类错误计数,方便量化"这个文件有多脏"。第 9 篇配置 VS Code 时,我们会在编辑器保存时自动跑 flake8,把检查前置到"打字那一刻"。 一个工程惯例值得在此建立:**代码提交(commit)前,先过一遍 `flake8` 与 `black`**——前者报问题,后者消问题。先把这条纪律变成习惯,后面第 22 章讲 CI 时会看到它如何进入自动化流水线。 ## 易错点与陷阱 **1. 类名大小写与"两空行"最容易被工具抓现行。** 习惯了其他语言驼峰的家伙会发现 `class myClass` 和 `class MyClass` 都能运行,于是风格检查成了唯一裁判。同样,模块级函数之间只空一行,功能完全正常,但 pycodestyle 会报 E302。这不是"工具苛刻",而是提醒你:**风格规则服务于人类阅读的节奏**,两空行把"顶层定义"与"函数内语句"的视觉层级区分开。养成提交前跑检查的习惯,这类问题就永远轮不到你手工找。 **2. 别把"格式"与"正确性"混为一谈。** flake8 报出的错误分两类:E/W 开头的是风格(能在格式上自动修复),F 开头的是可能的真实 bug(未使用导入、引了不存在的名字、重复键)。比如 F841 是"局部变量赋值后从未使用"——这往往是删改代码后留下的僵尸变量。看到 F 开头先当 bug 排查,而不是无脑忽略。**black 无法修复 bug**,它只负责排版,这一点很多人用 black 后用着用着就忘了 flake8 的价值,是个常见误区。 **3. black 与 PEP 8 的 79 字符之争。** black 默认 88 列,导致"PEP 8 说 79,black 说 88"的困惑。事实是:PEP 8 原文允许"团队内部约定可以适度放宽"(PEP 8 明确说行长限制是软约束,团队可统一调整),`flake8` 的默认 `--max-line-length` 也是 79,但几乎所有项目都会在配置里把 flake8 的行宽与 black 对齐,比如在 `setup.cfg` 或 `tox.ini` 写 `max-line-length = 88`。**关键是行宽在项目内统一**,数字本身不是正义。 **4. 命名约定里的"私有"不是真正的私有。** 前导下划线 `_internal` 只是约定,外部代码照样能访问;`__secret` 的 name mangling 只是改名而非隔离(`obj._ClassName__secret` 仍可读)。把"约定私有"当"强制私有"来依赖,代码会在意想不到的地方坏掉。真正要对外隐藏的,交由第 8 章面向对象里的 property 与封装机制处理。 ## 小结 PEP 8 是 Python 社区通过 PEP 流程(增强提案机制)发行的官方代码风格指南,本文拆解了它的三大类规则:布局(4 空格缩进、79 行长、两空行)、导入与空格(按标准库/第三方/本地分组、运算符两侧留白)、命名(snake_case 函数变量、CamelCase 类、UPPER_CASE 常量、下划线约定与 name mangling)。规范靠人执行不可靠,所以工程上引入工具:pycodestyle 逐条核对 PEP 8,black 直接改写排版(默认 88 行长),flake8 叠加静态检查(F 类错误往往是真实 bug 线索)。建立"提交前 flake8 + black"的纪律,比记住任何一条规则都重要。下一篇文章,我们进入调试的世界——用 print 与 pdb 给程序"看病"。 ## 练习与思考题 1. 打开你之前写的任何一段脚本,运行 `pycodestyle 文件名.py`,逐条读懂每个违规码的含义,再用 `black 文件名.py` 格式化,前后对比。至少记录 3 条你之前不知道的规则。(答案导向:格式错误集中在 E2xx/E3xx 布局类。) 2. 解释为什么 `if x == None:` 会被 PEP 8 社区当作坏味道,而 `if x is None:` 是对的。提示:`None` 是单例对象,`==` 会调用对象的比较逻辑。(答案导向:身份比较 vs 相等比较。) 3. 在你的虚拟环境中依次运行 `flake8 --statistics` 与 `black --check .` 两个命令,查阅 `--help` 弄清二者语义差异(一个是检查报告,一个是仅检查不改写),并说出哪些场景适合 `--check`。(答案导向:CI 流水线与提交前置检查。) 4. 综合运用:把一篇你写过的最长的脚本按本文规则重排(两空行、导入分组、命名修正、行长拆分),再用工具验证零违报。体会"手工排 + 机器验"与"纯 black 自动排"的体验差异。