代码里看到一个孤零零的 pass,我第一反应一般不是“这语法真优雅”,而是先看它是不是把异常吞了。
因为 pass 本身不干活。它最大的作用,就是在 Python 语法要求“这里必须有一段代码”的地方,先占个坑。
比如你写了一个函数,只把名字定下来,逻辑还没写:
defsync_order_status(order_id: str):
pass
这段代码能跑。
但如果你把 pass 删掉:
defsync_order_status(order_id: str):
解释器直接报错:
IndentationError: expected an indented block
Python 对缩进很认真。def、class、if、for、try 后面都必须有代码块。你可以不写业务逻辑,但不能空着。pass 就是告诉解释器:这里我知道要写代码,但我现在故意什么都不做。
看个稍微像业务点的例子。
我之前写导入脚本时,经常会先把流程骨架搭出来:
classOrderImportJob:
defread_file(self, path: str):
pass
defcheck_header(self, rows: list[dict]):
pass
defsave_orders(self, rows: list[dict]):
pass
defrun(self, path: str):
rows = self.read_file(path)
self.check_header(rows)
self.save_orders(rows)
这时候 pass 的作用很简单:先让代码结构跑起来。
但这玩意不能长期放着。最怕的就是这种代码合并进去了,测试也没覆盖到,线上某个分支走进去,然后什么都没发生。
更坑的是异常里写 pass。
defload_price(item_id: str) -> int:
try:
return int(open(f"/data/price/{item_id}.txt").read().strip())
except FileNotFoundError:
pass
这段代码看着没报错,实际问题更大。
调用方拿到的是 None。然后后面可能炸在一个完全不相关的位置:
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
这种问题排起来很烦,因为真正的原因早就被 pass 吃掉了。
我一般不这么写。哪怕暂时兜底,也会把行为写清楚:
defload_price(item_id: str) -> int:
file_path = f"/data/price/{item_id}.txt"
try:
with open(file_path, "r", encoding="utf-8") as f:
return int(f.read().strip())
except FileNotFoundError:
print(f"[price_missing] item_id={item_id}, path={file_path}")
return0
别嫌这个 print 土。脚本里跑批量数据时,一条明确日志比沉默强多了。
pass 还有一个常见场景,是先定义接口或者抽象结构。
classFileCleaner:
defclean(self, line: str) -> str:
pass
这代码能表达“以后这里会有清洗逻辑”。但如果真是要做接口约束,我更愿意让它直接报错:
classFileCleaner:
defclean(self, line: str) -> str:
raise NotImplementedError("clean() must be implemented")
区别很大。
pass 是悄悄跳过。
raise NotImplementedError 是明确告诉你:这里没实现,别装作能用。
写工具脚本时,这个判断挺关键。比如你有不同渠道的字段清洗器:
classCsvCleaner:
defclean(self, line: str) -> str:
cols = [x.strip() for x in line.split(",")]
return",".join(cols)
classExcelCleaner:
defclean(self, line: str) -> str:
raise NotImplementedError("ExcelCleaner is not ready")
这样上线前一跑就能发现问题,不会假装成功。
还有人会把 pass 和 continue 搞混。
这个差别很明显:
for row in rows:
ifnot row.get("order_id"):
pass
save_row(row)
这里的 pass 只是“不做任何事”,后面的 save_row(row) 还是会执行。
如果你的意思是跳过这一行,应该写:
for row in rows:
ifnot row.get("order_id"):
continue
save_row(row)
这类 bug 很隐蔽。代码看着像过滤了脏数据,实际一条没过滤。
再比如 if 里占位:
if amount < 0:
pass
else:
write_bill(amount)
这段代码语法没问题,但业务味道很怪。金额小于 0,既不报错,也不记录,也不返回。后面查账查到这种地方,基本要骂人。
我更倾向这样写:
if amount < 0:
print(f"[bad_amount] amount={amount}")
return
write_bill(amount)
代码不一定高级,但行为清楚。
所以 pass 的作用可以记成一句话:它是 Python 里的空语句,用来占住一个必须存在的代码块。
它适合临时代码骨架、空类、占位分支。
但它不适合长期放在异常处理里,也不适合假装处理了某个业务分支。
我看到 except: pass 基本会直接改。不是洁癖,是这种代码在线上太像一个黑洞:错误进去,日志没有,返回值变形,最后炸在别的地方。排查时你只能顺着调用链一层层翻,翻到最后发现,原来最早那行代码选择了沉默。