只捕获你真的能处理的那个错误,并且让 try 块窄到你一眼就知道是哪一步失败了。下面这些写法覆盖精确捕获、清理、抛出、重试,以及那种让程序悄悄失败的宽泛捕获。
🎙️ 发布并录制于: ·
try 里只放你预期会失败的那一步。把用户输入的文本转成数字,就捕 ValueError。异常的细节对用户有用时,用 as e 把它绑定下来。超时该重试、连接断了该报警,这两种就分开写处理分支。
try:
age = int(user_input)
except ValueError:
print("that wasn't a number")
try:
data = json.loads(text)
except json.JSONDecodeError as e:
print(f"bad JSON at line {e.lineno}: {e.msg}")
try:
resp = requests.get(url, timeout=5)
except requests.Timeout:
retry()
except requests.ConnectionError:
alert_ops()
except (ValueError, KeyError) as e:
log(e)
把几个异常类写成一个元组,只有在它们的处理方式完全一样时才合适。恢复方式不同,就分开写 except,让策略留在明面上。
else 只在 try 成功时执行。它把后续处理挪到受保护的块外面,这样 process 里的 bug 就不会被误认成打开文件失败。finally 不管成功、失败还是提前返回都会执行,所以适合放释放锁这类无条件的清理。
try:
f = open(path, encoding="utf-8")
except FileNotFoundError:
print("no such file")
else:
process(f.read())
f.close()
finally:
release_lock()
对文件来说,with 语句通常比手动 close 更稳。finally 最有价值的场景是资源没有上下文管理器,或者清理动作跨了好几步操作。
调用方给了一个你的函数没法接受的值,就抛 ValueError。在 except 里写一个裸的 raise,会把同一个异常连着原始 traceback 一起往上送。做重试时只捕获临时性的失败,每失败一次就多等一会儿,最后一次尝试之后重新抛出。
def set_age(age):
if age < 0:
raise ValueError(f"age can't be negative, got {age}")
try:
charge_card(order)
except PaymentError:
log_incident(order)
raise # same error and traceback
class QuotaExceeded(Exception):
pass
for attempt in range(3):
try:
result = flaky_api_call()
break
except requests.Timeout:
if attempt == 2:
raise
time.sleep(2 ** attempt) # 1 second, then 2
不要重试校验错误、权限失败,也不要重试刷卡被拒。反复重试一个永久性的失败只会拖时间,还可能把副作用做重一遍。重试策略应该属于那个知道再试一次安不安全的操作。
典型症状是,代码跑完了,但什么都没发生。去搜 except 冒号 pass,或者 except Exception 冒号 pass。这些行会把真正的 traceback 丢掉,包括 NameError: name 'user_id' is not defined 这类消息。调试期间先把这个捕获删掉,或者用 logger.exception 把 traceback 记下来。
NameError: name 'user_id' is not defined
try:
save_user(user_id)
except Exception:
pass # hides the NameError
# If a top-level worker truly must stay alive:
try:
run_one_job()
except Exception:
logger.exception("job failed") # records full traceback
logger.exception(...);它和 logger.error(e) 不一样,会带上 traceback。裸 except 的范围还要更大:连 KeyboardInterrupt 这类信号都会被它吃掉。不要为了保险把整个函数包起来。让错误往上冒,直到某一层能做出真正的决定:重试、报给用户,还是停下来。