写 Python 的方式:从「能跑」到「好读」
我见过不少 Python 项目,一开始跑得很溜,半年后没人愿意碰。不是因为语言本身,而是因为大家普遍把 Python 的灵活性用成了「先把功能堆上去、以后再说」的挡箭牌。这篇文章整理我认为最值得坚守的几条实践——不是语法速查,而是在长期维护中真正发挥作用的那些习惯。
可读性不是风格偏好,是工程成本
Python 之禅里有一句话:「可读性非常重要(Readability counts)」。这句话被贴烂了,但我发现大多数人只是嘴上认同,落到代码里仍然满眼是单字母变量和无名 lambda。
问题不在于短,而在于认知摩擦。
# 这个函数做什么?看了三秒还在猜
def proc(u, f=True):
return [x for x in u if x.a == f]
# 这个版本,读一眼就够了
def get_active_users(users: list[User], active: bool = True) -> list[User]:
"""Return users filtered by active status."""
return [u for u in users if u.is_active == active]类型注解不只是给 IDE 看的。它是一种文档化的参数契约,让三个月后的自己(或新同事)不必去翻调用链才能理解入参是什么形状。Python 3.9 之后可以直接用内建类型 list[str]、dict[str, int],不必再从 typing 导入。
给所有函数签名加类型注解,成本极低,但能把「要猜」变成「能看到」——这是可维护性回报最高的单项投入。
EAFP:Python 的错误处理哲学
Python 社区有个术语叫 EAFP(Easier to Ask Forgiveness than Permission),对应的反模式叫 LBYL(Look Before You Leap)。
LBYL 是先检查再操作:
if key in data:
value = data[key]EAFP 是直接做,失败了再处理:
try:
value = data[key]
except KeyError:
value = default两者在简单场景下区别不大,但在并发或 I/O 场景里 LBYL 有竞态风险:检查通过和实际操作之间状态可能已经变了。EAFP 天然规避了这一点。
更重要的是异常层级设计。许多项目里异常是一盘散沙,要么全抓 Exception,要么直接 except: pass——后者是最危险的,它吞掉了所有信号,包括 KeyboardInterrupt。正确的做法是建一个应用级别的异常基类,再按语义派生:
class AppError(Exception):
"""所有业务异常的根类型。"""
class NotFoundError(AppError): ...
class ValidationError(AppError): ...这样调用层可以按需粒度捕获,日志也能准确反映出错的类型,而不是统一写一条「something went wrong」。
生成器:惰性求值是默认正确选项
Python 列表推导是标志性语法,但新手常犯的错是把它用在不需要把全部数据装进内存的场景。
# 数据量大时,这一行就把几百 MB 全部吃进内存
lines = [line.strip() for line in open("huge.log")]
# 生成器版本:按需产出,内存占用恒定
def read_lines(path: str):
with open(path) as f:
for line in f:
yield line.strip()生成器和生成器表达式的原则很简单:只要下游不需要随机访问,就用生成器而不是列表。这不是过早优化,这是默认正确的选项。
同理,字符串拼接:
# 每次 += 都重新分配内存,O(n²)
result = ""
for item in items:
result += str(item)
# join 一次完成,O(n)
result = "".join(str(item) for item in items)上下文管理器:资源释放不该靠记忆
with 语句是 Python 最被低估的特性之一。它不只是「打开文件用 with」——任何需要成对操作(获取/释放、开始/结束、加锁/解锁)的场景都应该用上下文管理器来封装。
from contextlib import contextmanager
@contextmanager
def timer(label: str):
start = time.perf_counter()
yield
print(f"{label}: {time.perf_counter() - start:.3f}s")
with timer("数据处理"):
process_large_dataset()用 @contextmanager 装饰器比写完整的 __enter__/__exit__ 类要轻量很多,适合临时性的资源作用域。
一句话心法
写 Python 的最高标准不是「聪明」,而是「让下一个读代码的人不需要猜」。