2. try-except语句:基本语法、捕获单个异常、捕获多个异常、异常链

异常处理,说白了就是给代码上「保险」。

我刚开始写Python那会儿,总觉得try-except是多余的。代码跑得好好的,干嘛要加这些?直到有一次,我写了一个爬虫程序,跑了整整两天,结果因为一个网络超时,整个程序直接崩了。嗯,从那以后,我再也不敢不写异常处理了。

2.1 基本语法

try-except的结构其实很简单。你想想看,就是「试试看,不行就拉倒」的逻辑。

try:
    # 可能会出错的代码
    result = 10 / 0
except:
    # 出错后执行的代码
    print("出错了!")

我个人习惯把try块写得尽量小。为什么?因为范围越大,越难定位问题。你总不能把整个函数都塞进try里吧?

核心要点:

  • try块:放可能引发异常的代码
  • except块:异常发生时的处理逻辑
  • 如果没异常,except块不会执行

2.2 捕获单个异常

光知道「出错了」还不够。你得知道出了什么错。

try:
    number = int(input("请输入一个数字: "))
    result = 100 / number
except ValueError:
    print("你输入的不是数字!")
except ZeroDivisionError:
    print("不能除以零!")

我在项目中遇到过这种情况:用户输入了一个空字符串,int()直接抛ValueError。如果不指定异常类型,你根本不知道是输入问题还是计算问题。

我的建议:永远指定具体的异常类型。裸except就像闭着眼睛抓蚊子——你可能抓到了,但不知道抓的是什么。

2.3 捕获多个异常

有时候,不同的异常需要同样的处理方式。这时候可以合并捕获。

try:
    data = open("config.txt").read()
    value = int(data)
except (FileNotFoundError, ValueError) as e:
    print(f"读取配置失败: {e}")
    # 使用默认值
    value = 0

你看,文件不存在和内容格式不对,都是配置读取失败。把它们放一起处理,代码清爽多了。

还有一种写法,用元组把异常类型包起来:

try:
    # 一些操作
    pass
except (TypeError, ValueError, IndexError):
    print("常见的几种错误,统一处理")

注意:别把不相关的异常放一起。比如把KeyboardInterrupt和ValueError放一起,用户按Ctrl+C想退出程序,结果被你当成普通错误处理了——这就不对了。

2.4 异常链

异常链是个好东西,但很多人不会用。

什么叫异常链?就是一个异常引发了另一个异常。比如你读取文件时遇到编码错误,然后你想抛出一个更上层的「配置加载失败」异常。这时候,原始异常不能丢。

def load_config():
    try:
        with open("config.json", "r") as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise RuntimeError("配置加载失败") from e

try:
    config = load_config()
except RuntimeError as e:
    print(f"错误: {e}")
    print(f"原始原因: {e.__cause__}")

我曾经调试过一个线上问题,日志里只看到「数据库连接失败」,但根本不知道是网络问题还是认证问题。后来加了异常链,才发现是DNS解析超时。嗯,从那以后,我所有自定义异常都带上了原始异常。

Python 3里,异常链有两种方式:

方式 语法 效果
隐式链 except块中直接raise新异常 自动保留__context__
显式链 raise ... from ... 明确指定__cause__

我的经验:能用显式链就别用隐式链。显式链让代码意图更清晰,别人看代码时一眼就知道「哦,这个异常是由那个异常引起的」。

2.5 知识体系图

try-except语句 基本语法 捕获单个异常 捕获多个异常 try/except/else finally子句 指定异常类型 获取异常对象 元组捕获 统一处理 异常链 (raise ... from ...) 保留原始异常上下文

一个小技巧:调试时可以用traceback模块打印完整的异常链。这样你就能看到异常从哪来、到哪去,整个调用路径一目了然。

好了,try-except这块儿就这些。记住一个原则:异常处理不是用来掩盖错误的,而是用来优雅地处理错误的。别把try-except当成万能胶,哪儿都糊一层——那样只会让问题更难排查。

我曾经踩过的坑:在except块里又写了可能出错的代码,结果异常没处理好,反而引发了新的异常。记住,except块里的代码也要小心,最好只做简单的日志记录和资源清理。

可可精品资料库