我们学习一门语言,既要服务于我们所解决的问题, 也要对语言的设计者对设计该语言的初衷进行了解。"经验丰富的程序员倡导尽可能避繁就简。Python社区的理念都包含在Tim Peters撰写的 “Python之禅”中。要获悉这些有关编写优秀Python代码的指导原则,只需在解释器中执行命令 import this"(引用《Python编程:从入门到实践》),以下让我们对"Python之禅"的内容进行简单的翻译,没事儿看看,可能在不同的编程阶段会有不同的感悟。
bashThe Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
python
# ✅ 推荐:括号隐式换行 + 每行一个参数 + 可加尾随逗号
result = do_something(
user_id=uid,
retries=3,
timeout=5.0,
on_error=handle_error, # 行内注释也没问题
)
# ❌ 不推荐:反斜杠续行,注释/空格稍不注意就报错
result = do_something( \
user_id=uid, \
retries=3, \
timeout=5.0, \
on_error=handle_error \
)
python
# ✅ [] / {} 内天然支持多行
fields = [
"id", #用户ID
"name",
"email", # 轻松加注释
]
config = {
"host": "localhost", # 测试服IP
"port": 5432,
"debug": True,
}
python
# ✅ 推荐:把每个子条件单独一行,且操作符前置,便于增删
if (
is_logged_in
and has_permission("export")
and (items_count > 0)
):
export()
python
# ✅ 多个上下文管理器
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
):
fout.write(fin.read())
# ✅ 多个导入(小括号内自动换行)
from pathlib import (
Path,
PurePath,
)
python
# ✅ 相邻字符串字面量会在编译期合并
sql = (
"SELECT id, name, email "
"FROM users "
"WHERE active = 1 "
"ORDER BY created_at DESC"
)
# ✅ f-string 也可以拆成多个相邻 f-string
user_line = (
f"id={user.id} "
f"name={user.name} "
f"email={user.email}"
)
python
# ✅ 保持结构清晰
score = (
0.25 * precision
+ 0.25 * recall
+ 0.50 * f1
)
# ✅ 链式调用
(
pd.read_csv("data.csv")
.query("age >= 18")
.assign(is_vip=lambda df: df["score"] > 80)
.to_csv("out.csv", index=False)
)
python
from typing import Dict, List, Tuple
# ✅ 类型别名分行清晰
UserIndex = Dict[
int, # user_id
Tuple[str, List[str]] # (name, tags)
]
python
# ✅ 方括号推导式可多行(仍是列表)
names = [
user.name
for user in users
if user.active
]
# ✅ 小括号=生成器表达式,传给 sum 等函数更省内存
total = sum(
item.price
for item in items
if item.in_stock
)
python# ✅ 尾随逗号让 diff 更干净,后续新增行无需改上一行
settings = {
"timeout": 10, # seconds
"retries": 3,
"cache": True,
}
python
def build_payload(user):
return {
"id": user.id,
"name": user.name,
"roles": [r.name for r in user.roles],
"active": user.active,
}
pythontodo: 范式 A:长调用参数
resp = requests.post(
url,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"id": user.id,
"name": user.name,
"tags": user.tags,
},
timeout=10,
)
todo: 范式 B:长条件判断
if (
user.is_active
and user.email_verified
and not user.is_banned
and (quota.used < quota.limit)
):
allow_access()
todo: 范式 C:长字符串(SQL/日志模板)
query = (
"SELECT u.id, u.name, COUNT(o.id) AS orders "
"FROM users u "
"LEFT JOIN orders o ON u.id = o.user_id "
"WHERE u.created_at >= :start "
"GROUP BY u.id, u.name "
"ORDER BY orders DESC "
"LIMIT 100"
)
todo: 范式 D:分步计算/链式
final = (
normalize(raw)
.pipe(filter_invalid)
.pipe(enrich_with_geo)
.pipe(attach_metrics)
)
python
# ❌ 行尾注释与 \ 冲突
total = price + tax \ # 因为反斜杠后还有东西(注释),所以这个反斜杠失效,并不会把下一行接起来
+ fee # Python 会尝试单独解析 + fee 这一行,但这在语法上是不完整的表达式,直接报错: SyntaxError: invalid syntax
"""
1. \ 表示“当前行还没结束,请继续看下一行”。
2. 但是 \ 必须是这一行的最后一个字符,后面不能有空格、不能有注释。
否则 Python 认为 \ 只是一个普通字符,行就结束了。
"""
arr = a[1:5]不要加空格、如果切片表达式本身比较复杂,可以用 对称空格 提高可读性如arr = a[lower : upper + 1] | 类型 | 命名方式 | ✅ 示例 | ❌ 反例 | 说明 |
|---|---|---|---|---|
| 普通变量 | snake_case | max_value, user_name | MaxValue, maxValue | 统一小写+下划线;布尔变量常用 is_, has_ |
| 关键字冲突 | 变量末尾加 _ | class_, type_ | class, type | 避免与关键字/内置遮蔽 |
| 常量 | ALL_CAPS | MAX_RETRY, DEFAULT_TIMEOUT | MaxRetry, maxretry | 模块顶层定义;可配合 Final |
| 函数名 | snake_case | def get_user_info(): | def GetUserInfo(): | 动作/动宾短语;参数命名约定 self, cls |
| 类名 | CapWords(驼峰) | class UserProfile: | class user_profile: | 异常类常以 Error 结尾 |
| 模块名 | 全小写,可下划线 | user_service.py, config.py | UserService.py | 文件名简短可读 |
| 包名 | 全小写(不建议下划线) | services, utils | my_package | 导入路径简洁,生态约定 |
| 单前导下划线 | _name | _cache, _connect() | cache, connect | 约定为“内部使用”,非强制私有 |
| 双前导下划线 | __name | __balance, __compute() | balance, compute | 触发名称改写,避免子类冲突 |
| 双前后下划线 | __name__ | __init__, __str__ | __my_func__ | 保留“魔术方法”,禁止自造 |
| 异常类 | 驼峰,Error 结尾 | class ConfigError(Exception): | class Config(Exception): | 明确错误语义 |
| 布尔变量 | 语义前缀 | is_valid, has_permission | valid, permission | 可读性更强 |
| 集合/复数 | 复数形式 | users, items, orders | user_list, itemArray | 建议用含义清晰的复数名 |
| 测试函数 | test_ 前缀 | test_parse_config() | parse_config_test() | 与 pytest/工具链兼容 |
| 规则 | 要点 | ✅ 正例 | ❌ 反例 / 解释 |
|---|---|---|---|
| 函数尽量短小 | 以 100 行作为上限参考;更小更好 | 将长流程拆为若干私有/辅助函数 | 一个函数数百行、承担多种职责 |
| 单一职责 | 一个函数只做一件事;语句粒度保持一致 | parse_config() 只负责解析,不负责 IO/校验 | parse_config() 又读文件、又解析、又写 DB |
| 禁用可变类型作默认值 | 默认参数在定义时只评估一次 → 共享状态风险 | def f(x=None):\n if x is None:\n x = [] | def f(x=[]): ...(不同调用共享同一列表) |
| 禁止“共享可变默认值”套路 | 千万别把模块级列表/字典拿来当默认值 | DEFAULT_FACTORY = list + 在函数里 x = DEFAULT_FACTORY() | List = []\n def fun(num, arr=List): ...(明令禁止) |
提示
数据类可以用 default_factory
pythonfrom dataclasses import dataclass, field
@dataclass
class Bag:
items: list[int] = field(default_factory=list) # 每次实例化时都给我一个全新的空列表
| 规则 | 要点 | ✅ 正例 | 备注 |
|---|---|---|---|
self / cls 命名 | 实例方法首参用 self;类方法首参用 cls;静态方法无 self/cls | python\nclass User:\n def save(self): ...\n @classmethod\n def from_id(cls, uid): ...\n @staticmethod\n def ping(): ...\n | 你原文里的“类静态方法用 cls”应更正为类方法 |
| 关键字冲突的参数名 | 若与关键字冲突,末尾加下划线 _ | def select(from_: str): ... | 与变量命名冲突处理保持一致 |
| 圈复杂度 ≤ 10 | 以 10 为上限;超过就拆分 | 提取子函数、使用早返回、用查表/策略替代多层 if-elif | 可用 radon/ruff 扫描(团队 CI) |
1) 可变默认值
python# ❌ 错误:默认列表在定义时创建,后续调用共享同一对象
def append_item(item, bucket=[]):
bucket.append(item)
return bucket
append_item(1) # [1]
append_item(2) # [1, 2] ← 期望之外的“记忆效应”
# ✅ 正确:用 None 作为哨兵
def append_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
2) 圈复杂度控制与拆解
python# ❌ 复杂、嵌套多
def handle(order):
if order.valid:
if order.paid:
if order.stock_ok:
ship(order)
else:
refund(order)
else:
notify_to_pay(order)
# ✅ 早返回 + 小函数
def handle(order):
if not order.valid:
return reject(order)
if not order.paid:
return notify_to_pay(order)
return _fulfill(order)
def _fulfill(order):
if not order.stock_ok:
return refund(order)
return ship(order)
3) 单一职责与命名
python# ❌ 名不副实:既下载又解析又落库
def process_user_data(url): ...
# ✅ 明确边界
def fetch_user_csv(url): ...
def parse_user_csv(text: str) -> list[User]: ...
def save_users(users: list[User]) -> None: ...
| 规则/场景 | 要点 |
|---|---|
每行一个 import | import os / import sys,不要 import sys, os |
| import 位置 | 顶部(在模块注释/文档串之后、模块常量之前) |
| 分组顺序 | 标准库 / 第三方 / 本地应用(每组之间空一行) |
| 导入风格 | 优先 绝对导入,在包内可使用显式相对导入(谨慎) |
| 禁止 | from module import *(污染命名空间、影响静态分析) |
| 允许 | 为长名或约定重命名(import numpy as np) |
| 工具 | 使用 isort / ruff / black 自动化排序与格式化 |
导入语句应放在模块文件顶部(模块注释 / 文档字符串之后),并在模块全局常量/变量之前。例如:
python"""module docstring"""
import os
import typing
CONSTANT = 42
把 imports 分成三组,每组内部按字母序排列(工具会自动处理):
python# 标准库
import os
import sys
# 第三方
import requests
import numpy as np
# 本地应用/库
from myapp.utils import helper
虽然一般放顶部,但下列情形允许局部导入:
pythondef use_pandas():
import pandas as pd # 延迟导入,避免全局开销或循环依赖
return pd.DataFrame(...)
| 规则 | 强制 / 推荐 | 要点 |
|---|---|---|
接口注释(文档字符串)使用 """ | 强制 | 模块、类、公共函数/方法必须要有 docstring(接口注释) |
代码块注释使用 # | 强制 | 每行注释以 # 开始;每行 # 后跟 1 个空格 |
| 内联注释前至少两个空格 | 强制(PEP8) | 例如:x = x + 1 # 增加计数 (# 前至少 2 个空格) |
| 块注释独占一行并与代码对齐 | 强制 | 块注释通常与代码缩进对齐,不能放在代码行尾(除简短内联) |
| 公共 / 重要函数必须有 docstring | 强制 | docstring 需包含简介,必要时说明参数/返回/异常 |
| 逻辑复杂处必须注释 | 强制 | 复杂算法、边界条件、hack/兼容性等必须写注释并链接 issue/PR |
| ** TODO 注释** | 推荐 | 使用 # TODO:,最好附 issue 编号和责任人 |
| 注释应说明“为什么”而不是重复“做什么” | 推荐 | 注释解释设计动机/限制/副作用,而不是逐行解释可读代码 |
| 注释要随代码修改更新 | 强制(流程) | 代码变动时同步更新注释;PR 必检项之一 |
用途:模块 / 类 / 函数 / 方法 的接口文档 — 描述做什么、参数、返回值、异常、示例等。 格式建议:首行一句话概述;若有更多说明,首行后空行写详细描述。
Google 风格
pythondef find_user(user_id: int) -> dict | None:
"""Find a user by id.
Args:
user_id (int): The ID of the user to lookup.
Returns:
dict | None: The user dict if found, otherwise None.
Raises:
DatabaseError: If the DB query fails.
"""
...
模块 docstring(文件顶部)
python
"""utils.strings
Utilities for string normalization and tokenization used across the service.
"""
用途:标记待办、需要修复或不完美的解决方案。
| 标记 | 含义 | 典型用途 | 示例 |
|---|---|---|---|
| TODO | 待办事项 | 代码中需要实现的功能、优化、改进或重构 | # TODO(issue#123): refactor to use X strategy — @alice |
| FIXME | 待修复 | 表示当前代码有 bug、问题或不可靠,需要修复 | # FIXME: this workaround fails on Windows |
| HACK | 临时解决方案 / 权宜之计 | 表示代码写得不够优雅或安全,是临时方案,需要优化或替换 | # HACK: using sleep to wait for API, should use event listener |
| NOTE | 说明 / 提示 | 给阅读代码的人提供额外信息、设计思路或注意事项 | # NOTE: this value is hardcoded for compatibility with legacy system |
python
def process_data(data):
# TODO(issue#101): add input validation — @bob
# FIXME: fails if data contains None
# HACK: using global cache to speed up, need proper caching later
# NOTE: this function is called by both sync and async tasks
...
在团队中统一格式,比如都加 issue# 和负责人,可以用 CI/脚本统计 TODO/FIXME/HACK 数量,避免遗漏。
| 类型 | 空值 | 布尔值 | 推荐写法 | 不推荐写法 |
|---|---|---|---|---|
列表 list | [] | False | if not user_list: / if user_list: | if len(user_list) == 0: / if len(user_list): |
元组 tuple | () | False | if not my_tuple: / if my_tuple: | if len(my_tuple) == 0: |
字典 dict | {} | False | if not my_dict: / if my_dict: | if len(my_dict) == 0: |
集合 set | set() | False | if not my_set: / if my_set: | if len(my_set) == 0: |
字符串 str | '' | False | if not my_str: / if my_str: | if len(my_str) == 0: |
理由:
示例:
pythonuser_list = []
# 推荐
if not user_list:
print("列表为空")
if user_list:
print("列表不为空")
# 不推荐
if len(user_list) == 0:
print("列表为空")
if len(user_list):
print("列表不为空")
| 判断方式 | 推荐性 | 示例 | 理由 |
|---|---|---|---|
is None / is not None | ✅ 推荐 | if value is None: / if value is not None: | 显式判断,逻辑清晰,避免误判空字符串、0 或空列表 |
if not value is None | ❌ 不推荐 | if not value is None: | 冗余且可读性差 |
if value: | ⚠️ 小心 | 只在你希望 None / 0 / '' / [] 都视作 False 时使用 | 用于一般真值判断,不用于严格 None 判断 |
示例:
python
value = None
# 推荐
if value is None:
print("value 是 None")
if value is not None:
print("value 有值")
# 不推荐
if not value is None:
print("不要这样写")
| 情况 | 推荐写法 | 不推荐写法 | 理由 |
|---|---|---|---|
True / False 判断 | if greeting: / if not greeting: | if greeting is True: / if greeting == True: | Python 内置布尔类型可直接用作条件判断,冗余比较会降低可读性 |
示例:
python
greeting = True
# 推荐
if greeting:
print("Hello")
# 不推荐
if greeting is True:
print("Hello")
if greeting == True:
print("Hello")
pythonuser_list = []
greeting = True
value = None
# 容器判断
if not user_list:
print("列表为空")
# 布尔类型判断
if greeting:
print("问候开启")
# None 判断
if value is None:
print("值未初始化")
# 结合使用
if user_list and greeting and value is not None:
print("执行逻辑")
| 规则 | 强制 / 推荐 | 说明 |
|---|---|---|
| 捕获不可预知的异常 | 强制 | 如网络请求、文件读写、IO 操作可能失败的场景,使用 try-except 保证流程正常 |
| 异常捕获颗粒度应细 | 强制 | 尽量对单行或单操作捕获异常,避免将大段逻辑放在一个 try 块中 |
| 区分稳定代码与非稳定代码 | 强制 | 稳定代码指理论上不会出错的逻辑,如简单变量赋值,不建议放入 try |
| 异常不能作为流程控制 | 强制 | 条件判断不要用异常来代替 if-else |
| 捕获后必须处理或抛出 | 强制 | 禁止捕获异常后直接 pass,如果无法处理,应 raise 给调用者 |
finally 中禁止使用 return | 强制 | finally 中 return 会覆盖正常流程的返回值,导致逻辑混乱 |
| 场景 | 推荐写法 | 说明 |
|---|---|---|
| 简化复杂嵌套逻辑 | 使用 try-except 替代深层 if-else | 将易出错的操作放入 try,简化层层判断 |
| 可预检查 | 推荐先用判断条件规避异常 | 例如检查文件是否存在,再打开 |
| 错误返回 | 推荐抛出异常类而非返回错误变量 | 便于上层统一捕获和处理异常 |
单操作异常捕获(颗粒度小)
pythontry:
value = int(user_input)
except ValueError as e:
print(f"输入错误: {e}")
大段 try 块(不推荐)
python# ❌ 错误示例
try:
value = int(user_input)
result = 10 / value
with open("file.txt") as f:
data = f.read()
except Exception as e:
print("出错了") # 不明确,不利于排查
finally 禁止 return
pythondef test_finally():
try:
return 1
finally:
return 2 # ❌ 会覆盖 try 的返回值
print(test_finally()) # 输出 2,而非 1
推荐的 finally 用法
pythondef test_finally_correct():
try:
return 1
finally:
print("执行清理操作") # 仅做清理,不 return
print(test_finally_correct()) # 输出 1
推荐项目结构示例如下:
markdownsample_project
├── readme.md
├── docs
│ ├── api.yml
│ └── public_read.md
├── requirements.txt
├── app
│ ├── __init__.py
│ ├── core.py
│ └── helpers.py
├── config
│ ├── mysql.py
├── deploy
│ ├── __init__.py
│ └── run.sh
├── db
│
├── utils
│
└── tests
├── __init__.py
└── test_basic.py
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
readme.md | 项目说明文件,通常包含项目简介、安装方法、使用方法、目录结构、贡献指南等 | 必须存在,方便开发者快速了解项目 |
requirements.txt | 列出项目依赖的 Python 包及版本 | 可以用 pip freeze > requirements.txt 或手动维护版本号 |
docs/ | 存放项目文档,如功能说明、API 文档、设计文档 | 可存放 Markdown、OpenAPI(api.yml)等文档,便于开发和运维 |
tests/ | 单元测试目录,存放测试用例 | 每个子模块建议对应一个测试文件,便于覆盖功能模块 |
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
__init__.py | 标记 app 为包 | 可为空,或初始化包级逻辑 |
core.py | 核心业务逻辑模块 | 放主要功能代码 |
helpers.py | 工具函数、辅助方法 | 避免把工具函数混入核心逻辑,提高可维护性 |
提示
核心目录 app/ 是业务逻辑的中心,建议按照功能模块拆分子目录,例如 app/user/、app/order/ 等。
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
mysql.py | 存放数据库相关配置 | 可以放不同环境配置,如 dev.py、prod.py,便于环境切换 |
redis.py(可选) | 存放 Redis、缓存等配置 | 分离配置和业务逻辑,遵循 12-factor App 原则 |
提示
配置文件一般只存放静态配置或常量,不应包含业务逻辑。
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
run.sh | 部署脚本,例如启动、初始化、迁移数据库 | 可写成 shell 脚本、docker-compose 或 Ansible playbook |
__init__.py | 如果需要作为包导入部署脚本 | 通常可为空 |
| 其他 | 可以放 docker-compose.yml、k8s 配置等 | 便于统一部署管理 |
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
db/ | 存放数据库相关代码、SQL 脚本或 ORM 模型 | 可以拆分为 db/migrations、db/models,便于管理 |
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
utils/ | 存放项目通用工具函数、独立于业务逻辑的模块 | 例如日志工具、数据转换函数、通用验证方法等 |
| 命名 | 模块和函数命名清晰、简短 | 避免和业务模块混淆 |
| 文件 / 目录 | 说明 | 建议 |
|---|---|---|
__init__.py | 标记为包 | 可为空 |
test_basic.py | 测试文件 | 每个模块/功能对应一个测试文件 |
| 测试风格 | 使用 pytest 或 unittest | 测试应覆盖核心逻辑,尽量保证 CI/CD 通过率 |
本文作者:精卫
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!