在 Python 解释器里敲一行 import this,屏幕会刷出 19 条英文短句——这就是著名的 Zen of Python(PEP 20)。它不是空泛的口号,而是过去二十多年里 Python 社区对"好代码"的共识提炼。理解这些原则,能帮你写出别人愿意读、愿意接手的代码。
一个 Easter Egg 的来历
1999 年,Tim Peters 在 Python 邮件列表里用半开玩笑的语气把社区长期讨论的编码哲学浓缩成了 19 条格言,发到列表后立刻被收录为 PEP 20。Python 之父 Guido van Rossum 原本也写了一条,但那条太长,最终没被采纳——于是你今天看到的第 13 条里那句"unless you're Dutch"就是 Tim 对 Guido 的调侃。
这 19 条被藏进解释器成了 Easter Egg:输入 import this 即可看到全文。运行一下:
import this
输出正是那 19 条原则。下面逐组拆解它们在工程中的含义。
可读性优先:前七条在说什么
前七条围绕一个核心——代码是给人读的:
| 原则 | 工程含义 |
|---|---|
| Beautiful > ugly | 代码排版、命名、结构要让人一眼觉得"舒服" |
| Explicit > implicit | 不要靠魔法上下文推断行为,把意图写出来 |
| Simple > complex | 能用线性逻辑解决,就不要引入多余抽象 |
| Complex > complicated | 问题本身复杂时,用清晰的结构应对,而非把结构搅成一团 |
| Flat > nested | 减少嵌套层级,扁平的调用链更易追踪 |
| Sparse > dense | 一行别塞太多操作,给眼睛留呼吸空间 |
| Readability counts | 可读性不是锦上添花,是硬指标 |
这些原则的落地方式很具体:函数不超过 20 行、缩进不超过 3 层、一行只做一件事、用完整单词命名而非缩写。
实用主义与错误处理:第 8–12 条
- Special cases aren't special enough to break the rules(第 8 条):别为某个"特殊场景"单独开一条逻辑分支,尽量用通用路径覆盖。
- Although practicality beats purity(第 9 条):通用路径做不到时,务实优先。比如
str.format()并不完美,但比纯字符串拼接实用。 - Errors should never pass silently / Unless explicitly silenced(第 10、11 条):异常必须被看见;如果确实要吞掉,必须用显式的
except ...: pass并写注释说明原因。 - In the face of ambiguity, refuse the temptation to guess(第 12 条):API 设计遇到歧义时,宁可抛异常或要求显式参数,不要"聪明地"替用户猜。
唯一明显之道与时机判断:第 13–16 条
第 13 条"There should be one—and preferably only one—obvious way to do it"是 Python 与 Perl 文化分野的标志:Python 倾向提供一种标准做法,而非十种等价写法。第 14 条的 Dutch 段子提醒你——"唯一明显"对新手未必明显,文档和示例才是让之道变得明显的手段。
第 15、16 条讲时机:Now is better than never,但 never is often better than right now。翻译成工程决策——该做的事不要拖延,但仓促上线比不上暂缓打磨。发版前问自己:测试覆盖够了吗?回滚方案有吗?
设计可解释性与命名空间:第 17–19 条
- If the implementation is hard to explain, it's a bad idea(第 17 条):如果你无法在 30 秒内向同事讲清楚某段代码的原理,这段代码就该重写。
- If the implementation is easy to explain, it may be a good idea(第 18 条):反过来,容易解释不代表一定好,但至少通过了第一道筛选。
- Namespaces are one honking great idea(第 19 条):模块、包、类就是命名空间。用它们隔离职责,别把所有函数扔进一个文件。
用代码对比:遵循与违背之禅
下面用一段真实场景演示"违背之禅"的代码如何被改写为"遵循之禅"的版本。场景:从 API 拉取用户列表,过滤活跃用户,返回其邮箱。
违背版本——密集、隐式、嵌套、吞异常:
import requests
def get_emails(url):
try:
d = requests.get(url).json()
except:
d = []
return [u['e'] for u in d if u.get('a') and u.get('e')]
问题清单:变量名 d/u/e/a 隐晦;裸 except 吞掉所有异常;一行列表推导塞了过滤+取值+短路判断;嵌套隐式逻辑让人猜 a 是 active、e 是 email。
遵循版本——显式、扁平、可读、错误不静默:
import requests
def fetch_active_user_emails(api_url: str) -> list[str]:
"""从 API 获取活跃用户的邮箱列表。
Args:
api_url: 用户列表接口地址。
Returns:
活跃用户的邮箱列表。API 请求失败时抛出异常。
"""
response = requests.get(api_url)
response.raise_for_status() # 网络或服务端错误,绝不静默
users = response.json()
active_emails = []
for user in users:
is_active = user.get("active", False)
email = user.get("email")
if is_active and email:
active_emails.append(email)
return active_emails
# 使用示例
if __name__ == "__main__":
emails = fetch_active_user_emails("https://api.example.com/users")
print(emails)
改动要点:
| 之禅原则 | 改动 |
|---|---|
| Explicit > implicit | 变量名、参数类型、返回类型全写明 |
| Sparse > dense | 用 for 循环替代一行推导,每步清晰 |
| Errors never pass silently | raise_for_status() 让 HTTP 错误立刻可见 |
| Readability counts | 加 docstring,调用者不用猜返回格式 |
| Flat > nested | 无嵌套条件,逻辑线性展开 |
把之禅变成日常习惯
之禅不是背诵题,而是每次写代码时的自检清单。建议这样做:
- 提交前跑一次
import this——不是看输出,而是对照最近写的函数,逐条问自己是否违背。 - 命名审查——如果同事需要问你"这个变量什么意思",改名。
- 异常审查——搜索代码里所有
except:和except Exception:,确认每个都有显式处理或注释说明。 - 嵌套审查——缩进超过 3 层的函数,拆成子函数或用 early return 扁平化。
- 一行审查——超过 80 字符且包含多个逻辑操作的表达式,拆开写。
最后记住第 9 条:practicality beats purity。之禅是方向,不是教条。当项目有紧迫交付压力时,先保证核心路径可读、可测、可回滚,再逐步打磨其余部分。之禅的终极目标不是完美代码,而是让团队里每个人都能高效协作的代码。