异常处理

1. 知识点

核心概念

Python 中所有异常都继承自 BaseException(常用的是 Exception)。当代码出错时 Python “抛出”(raise)一个异常对象,你可以用 try 块"捕获"(catch)它。

关键表格对比

关键字作用执行时机是否必须
try包裹可能出错的代码始终执行
except捕获并处理特定异常仅当 try 块中抛出匹配异常时是(最少一个)
else无异常时执行的代码try 块没有抛出异常时
finally清理收尾工作无论如何都会执行(哪怕有 return)
raise主动抛出异常调用时立即抛出

try/except 三种写法对比

写法示例适用场景风险
捕获具体异常except ValueError:知道会出什么错低,精准处理
捕获多个异常except (ValueError, KeyError):两类错处理方式相同
裸捕获except:except Exception:兜底保护中高:可能掩盖意料之外的 bug

捕获越具体越好,裸 except: 只在最外层兜底使用。

常见异常速查表

异常类触发场景示例
ZeroDivisionError除以零10 / 0
ValueError类型对但值不对int("abc")
TypeError类型不匹配"hello" + 5
KeyError字典键不存在d["no_such_key"]
IndexError列表索引越界lst[999]
FileNotFoundError文件不存在open("不存在的文件.txt")
AttributeError属性或方法不存在"abc".non_exist()
ImportError模块导入失败import 不存在的模块

2.代码示例:

示例一:基础try/except——除法保护

def safe_divide(a,b):
    """安全排除法"""
    try:
        result = a / b
    except ZeroDivisionError:
        print("错误,除数不能为0")
        return None
    except TypeError:
        print("错误,请输入数字类型")
        return None
    else:
        print(f"计算成功:{a} / {b} = {result}")

if __name__ == '__main__':
    #测试三种情况
    safe_divide(10,2)
    safe_divide(10,0)
    safe_divide(10,"a")

代码流程说明try 块逐行执行,一旦某行出错立刻跳到匹配的 except;如果全部顺利执行完,进入 else

示例二:finally — 无论如何都要执行的清理


def read_file_with_cleanup(path):
    """演示 finally:无论成功失败都要关闭文件"""
    f = None                    # 先置空,确保 finally 中可判断
    try:
        f = open(path, "r", encoding="utf-8")  # 打开文件
        content = f.read()      # 读取内容
        print(f"读取成功,文件长度:{len(content)} 字符")
        return content          # 注意:即使 return,finally 也会执行!
    except FileNotFoundError:
        print(f"文件不存在:{path}")
    except PermissionError:
        print(f"没有权限读取:{path}")
    finally:
        if f:                   # 如果文件成功打开了
            f.close()           # 确保关闭
            print("文件已关闭(finally 执行)")

if __name__ == '__main__':
    read_file_with_cleanup("不存在的文件.txt")
    print("-"*30)
    result = read_file_with_cleanup(__file__)#读取自身
    print(f"返回值:{result[:50]}")

关键点:即使 try 里有 returnfinally 也会在函数真正返回之前执行——这是 Python 的保证。

示例3:主动抛出异常+自定义异常


class AgeError(Exception):
    """自定义异常,年龄不合法"""
    def __int__(self,age,message="年龄必须在0—150之间"):
        self.age=age
        self.message=f"{message}(你输入了{age})"
        super().__int__(self.message)

def set_age(age):
    """设置年龄,不合法时抛出AgeError"""
    if not isinstance(age,int):
        raise TypeError(f"年龄必须是整数,不能是{type(age).__name__}")
    if age < 0 or age >150:
        raise AgeError
    print(f"年龄设置为{age}岁")
    return age

test_values = [25,-5,"三十岁"]
for val in test_values:
    try:
        set_age(val)
    except AgeError as e:        # 捕获自定义异常,打印详细信息
        print(f"[自定义异常]{e}")
    except TypeError as e:
        print(f"[异常类型]{e}")
    print("--------------")

设计思路AgeError 继承 Exception,带上了出错的 age 值和描述信息,让调用方能拿到丰富上下文。

示例4:异常链 — raise…from


def load_config(path):
    """加载配置文件,底层异常通过raise...from传递"""
    import json
    try:
        with open(path,"r") as f:
            return json.load(f)
    except FileNotFoundError as e:
        #把底层异常包装成更高级的异常,保留原始原因
        raise RuntimeError(f"配置文件{path}不存在") from e
    except json.JSONDecodeError as e:
        raise RuntimeError(f"配置文件{path} JSON格式错误") from e

try:
    load_config("不存在的配置.json")
except RuntimeError as e:
    print(f"捕获到:{e}")
    print(f"原始原因:{e.__cause__}")

raise...from 的作用:保留原始异常的调用栈信息,方便调试时追溯根因。

补充知识点

  • 异常不是控制流:不要用 try/except 代替 if 检查。比如用 if key in dict 而不是 try: dict[key] except KeyError——前者更清晰且性能更好。

  • except Exception vs 裸 except:裸 except 会捕获 KeyboardInterruptSystemExit,可能导致你的程序连 Ctrl+C 都无法终止。永远优先用 except Exception

  • 异常链的价值raise NewError(...) from original_error 保留完整的因果链,比简单 raise NewError(...) 更容易定位根因。

  • try/finally 模式:管理资源(文件、网络连接、锁)时,优先用 with 语句(自动调用 __exit__),而不是手写 try/finallywith 本身就是对 try/finally 的优雅封装。

更多推荐