异常处理基础:try-except 语句
写代码这事,没人能保证永远不出错。你想想看,网络断了、文件丢了、用户输了个奇怪的值……这些情况随时可能发生。我刚做开发那几年,最头疼的就是程序跑着跑着突然崩掉,留下一堆晦涩的 traceback。后来我养成了一个习惯:任何可能出错的代码,都先给它套个 try。
说白了,异常处理不是什么高深莫测的东西。它的核心就一句话:
「预测可能出错的地方,提前想好怎么兜底。」
基本语法:try-except 长什么样?
最简单的写法就像这样:
try:
# 尝试执行的代码
result = 10 / int(input("请输入一个数字: "))
print("结果是:", result)
except ZeroDivisionError:
# 出错了怎么办
print("不能除以零啊,兄弟!")
except ValueError:
print("你输入的不是数字吧?")
你看,try 块里放的是「有风险」的操作。万一 int() 转换失败,或者分母为零,Python 不会直接崩溃——它会跑到对应的 except 块里,执行你写好的补救逻辑。
核心原则
try只包住可能出错的代码,别把整个函数都塞进去- 每个
except只捕获你预期的那一种异常 - 剩下的异常让程序正常崩溃,比吃掉错误强百倍
捕获多种异常:按顺序匹配
一个 try 后面可以跟多个 except。Python 会从上到下检查异常类型,匹配到第一个就停下。我曾经在项目里犯过一个低级错误:把 Exception 写在了最前面,结果后面所有的细化捕获都成了摆设。唉,Debug 了两小时才发现。
正确的做法:子类异常放前面,父类异常放后面。
try:
data = fetch_from_network()
parse_data(data)
except ConnectionError:
# 网络问题 - 重试
retry()
except ValueError:
# 数据格式问题 - 记录日志
log_error()
except Exception as e:
# 其他未知错误 - 兜底
log_critical(e)
| 常见异常类型 | 使用场景 | 建议动作 |
|---|---|---|
ZeroDivisionError |
除法运算除数可能为零 | 提示用户输入有效值 |
FileNotFoundError |
读取不存在的文件 | 检查路径或创建默认文件 |
KeyError |
字典中缺失键 | 用 dict.get() 替代 |
AttributeError |
调用对象不存在的属性 | 检查对象类型或使用 hasattr() |
踩坑记录——我曾经……
在一个支付服务里,我图省事写了 except: 什么都不指定。结果上线后,KeyboardInterrupt 和 SystemExit 也被吞了,导致运维没法正常停服。从那以后我规定:永远不要写裸的 except: ——至少写成 except Exception:,它不会捕获系统级异常。
else 和 finally:完美的补充
else 块:只有当 try 块没有抛出任何异常时,才执行。我喜欢用它放「成功后要做的清理或确认」。
try:
file = open("report.txt", "r")
content = file.read()
except FileNotFoundError:
print("文件不存在,创建新文件...")
file = open("report.txt", "w")
else:
# 文件正常读取成功
print("文件读取完毕,长度:", len(content))
finally:
# 不管怎样,关闭文件
if 'file' in locals():
file.close()
finally 是个老实人——无论 try 里是正常结束、return、break 还是抛异常,它都会执行。我习惯把「释放资源、关闭连接、解锁」这类操作放在 finally 里,确保不会漏掉。
💡 我的个人习惯:
- 打开文件/数据库连接后,立刻在
finally里写关闭 - 如果逻辑简单,用
with语句能自动释放资源,更省心
实战建议与避坑指南
在实际项目中,我看过太多人把异常处理写得稀碎。给你几个我踩过的坑和总结的经验:
- 异常粒度要适中。别一个
try包几百行代码——出了错你根本不知道是哪个变量出了问题。 - 别吞异常。我见过有人
except: pass完事,结果 bug 藏了两周才发现。至少记个日志:logging.exception("发生错误")。 - 考虑
raise重抛的场景。有时候你只想记录一下异常,然后继续往上抛——用raise不带参数,可以保留原始堆栈。 - 自定义异常?建议业务逻辑里用。比如网络超时、数据校验失败,继承
Exception写个自己的类,调用方一眼就知道轻重缓急。
class PaymentTimeout(Exception):
"""支付接口超时"""
pass
try:
result = pay_service.charge(amount)
except PaymentTimeout:
# 专门处理超时
log_and_retry()
💡 一个小技巧:调试阶段我经常在 except 里加 traceback.print_exc(),等上线前再改成正式日志。这样既能快速定位问题,又不会把敏感信息暴露给用户。