异常处理基础: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 执行流程 try-except 执行流程 开始执行 try 块 执行 try 内代码 是否发生异常? 是 否 匹配 except 块 执行 except 代码 汇聚 执行 else 块(可选) finally 块(总是执行) 结束 说明 • 异常时:跳过 else,执行 except • 无异常时:跳过 except,执行 else • finally 不论如何都会执行 • 如果无 except 匹配,异常向上抛

实战建议与避坑指南

在实际项目中,我看过太多人把异常处理写得稀碎。给你几个我踩过的坑和总结的经验:

  1. 异常粒度要适中。别一个 try 包几百行代码——出了错你根本不知道是哪个变量出了问题。
  2. 别吞异常。我见过有人 except: pass 完事,结果 bug 藏了两周才发现。至少记个日志:logging.exception("发生错误")。
  3. 考虑 raise 重抛的场景。有时候你只想记录一下异常,然后继续往上抛——用 raise 不带参数,可以保留原始堆栈。
  4. 自定义异常?建议业务逻辑里用。比如网络超时、数据校验失败,继承 Exception 写个自己的类,调用方一眼就知道轻重缓急。
class PaymentTimeout(Exception):
    """支付接口超时"""
    pass

try:
    result = pay_service.charge(amount)
except PaymentTimeout:
    # 专门处理超时
    log_and_retry()

💡 一个小技巧:调试阶段我经常在 except 里加 traceback.print_exc(),等上线前再改成正式日志。这样既能快速定位问题,又不会把敏感信息暴露给用户。