Python3 异常处理

Python3 异常处理
一、核心概念:错误 vs 异常
| 类别 | 标识特征 | 触发阶段 | 典型示例 | 处理方式 |
|---|---|---|---|---|
| 语法错误(Syntax Error) | 编译期错误,程序无法运行 | 代码解析阶段(运行前) | 缺少冒号:if 1>0 print("err");缩进错误:def f():print("err");括号不匹配:print("hello") |
备注:直接修正语法,严格遵循Python规范(如冒号、缩进、括号匹配) |
| 异常(Exception) | 运行时错误,程序中断(未处理时) | 代码执行阶段(运行中) | 除零:10/0;索引越界:[1,2][5];类型错误:"2"+2;访问不存在变量:print(undefined_var) |
备注:通过try-except捕获处理,或raise主动抛出,或自定义异常类适配业务场景 |
二、异常处理核心语法(重点)
1. 基础捕获:try-except-else-finally
核心作用:捕获运行时异常,避免程序崩溃,实现可控的异常处理逻辑
try:
# 包裹可能触发异常的代码块(核心监控区域)
num = int(input("请输入一个数字:"))
result = 10 / num #示例:可能触发ZeroDivisionError(除零)或ValueError(非数字输入
except ZeroDivisionError as e:
# 捕获指定异常类型,e为异常对象(存储错误详情)
print(f"错误:{e}(除数不能为0)")
except ValueError as e:
# 捕获多个不同类型的异常,分别处理
print(f"错误:{e}(请输入有效的整数)")
else:
# 备注:仅当try块无任何异常时执行,处理正常逻辑
print(f"计算结果:10 ÷ {num} = {result}")
finally:
# 备注:无论是否发生异常,必然执行(释放资源的首选场景)
print("程序执行完毕(finally分支:资源已释放)") # 示例:关闭文件`file.close()`、数据库连接`conn.close()`
2. 主动抛出异常:raise
核心作用:手动触发异常,适配自定义校验场景(如参数合法性、业务规则校验)
def check_age(age):
"""校验年龄合法性(0-150岁)"""
if not isinstance(age, int):
# 抛出类型错误,提示参数类型异常
raise TypeError("年龄必须是整数类型") # 示例1:触发TypeError
if age < 0 or age > 150:
# 抛出值错误,附带具体错误描述
raise ValueError(f"年龄{age}无效(合法范围:0-150岁)") # 示例2:触发ValueError
print(f"年龄{age}校验通过")
# 测试主动抛出异常
try:
check_age(200) # 触发ValueError
# check_age("25") # 触发TypeError
except (TypeError, ValueError) as e:
print(f"捕获异常:{e}")
3. 自定义异常类
核心作用:适配业务专属异常场景(如登录失败、密码错误),提升代码可读性和维护性
备注:自定义异常类必须继承 Exception 基类(所有内置异常的父类),可重写构造方法和字符串展示方法
# 自定义业务异常类:密码相关异常
class PasswordError(Exception):
"""自定义密码异常类(适用于密码校验场景)"""
def __init__(self, msg):
# 初始化异常描述信息
self.msg = msg #示例:接收"密码长度不足"等具体描述
def __str__(self):
# 重写异常打印格式,提升可读性
return f"【密码异常】{self.msg}"
# 自定义业务异常类:登录相关异常
class LoginError(Exception):
"""自定义登录异常类(适用于登录校验场景)"""
def __init__(self, username, msg):
self.username = username # 附加用户名信息
self.msg = msg
def __str__(self):
return f"【登录异常】用户{self.username}:{self.msg}"
# 使用自定义异常
def check_password(pwd):
if len(pwd) < 6:
raise PasswordError("密码长度不能少于6位") # 触发自定义PasswordError
if not any(char.isdigit() for char in pwd):
raise PasswordError("密码必须包含至少一位数字") # 触发自定义PasswordError
print("密码校验通过")
def user_login(username, pwd):
try:
check_password(pwd)
except PasswordError as e:
# 捕获自定义异常,可进一步处理或重新抛出
raise LoginError(username, e.msg) from e # 备注:`from e`保留异常链,便于调试
print(f"用户{username}登录成功")
# 测试自定义异常
try:
user_login("zhangsan", "12345")
except LoginError as e:
print(e) # 输出:【登录异常】用户zhangsan:密码长度不能少于6位
三、常见内置异常(高频场景)
| 异常类型 | 触发场景 | 示例代码 | 备注 |
|---|---|---|---|
| ZeroDivisionError | 除零运算 | 10 / 0、5 // 0 |
算术运算中除数为0的专属异常 |
| NameError | 访问未定义的变量/函数 | print(undefined_var)、func_not_defined() |
变量未声明或作用域错误导致 |
| TypeError | 数据类型不兼容的操作 | "2" + 2、[1].append("str")、int(None) |
操作与数据类型不匹配 |
| IndexError | 序列(列表/元组/字符串)索引越界 | [1,2,3][5]、("a","b")[3]、"hello"[10] |
索引超出序列长度(Python索引从0开始) |
| KeyError | 字典访问不存在的键 | {"name":"Tom"}["age"]、dict.get("key", "default")(无默认值时) |
字典中无该键,需先判断或使用get()方法 |
| FileNotFoundError | 打开不存在的文件 | open("nonexist.txt", "r") |
文件路径错误或文件不存在 |
| ValueError | 数值转换失败或参数逻辑无效 | int("abc")、float("xyz")、list.remove(99)(列表中无99) |
参数类型正确但值不符合逻辑 |
| AttributeError | 访问对象不存在的属性/方法 | "hello".nonexist_method()、class Instance: pass; Instance.attr |
对象无此属性或方法(拼写错误常见) |
| ImportError | 导入模块失败 | import non_exist_module、from package import wrong_module |
模块未安装或路径错误 |
极简记忆口诀(背会这几句,覆盖 90% 场景)
除零用 Zero,类型错 Type,值无效 Value;名字错 Name,索引错 Index,键错 Key;文件没找到 FileNotFound,属性错 Attribute。
| 口诀片段 | 触发场景(一句话记) |
|---|---|
除零用 Zero |
除法/取模运算中,除数为0 |
类型错 Type |
操作/函数传入“类型不匹配”的参数 |
值无效 Value |
参数“类型正确但值不符合逻辑” |
名字错 Name |
访问“未定义/未赋值”的变量或函数 |
索引错 Index |
列表/元组/字符串的索引超出范围 |
键错 Key |
访问字典中“不存在的键” |
文件没找到</u>FileNotFound |
打开的文件不存在,或文件路径错误 |
属性错 Attribute |
访问对象“不存在的属性或方法” |
四、异常处理最佳实践(重点)
- 精准捕获异常,避免广谱捕获优先捕获具体异常(如
ZeroDivisionError),而非直接捕获Exception或省略异常类型(except:),防止掩盖未知错误(如语法错误、系统级异常)。反例:
try:
10 / 0
except: # 错误:捕获所有异常,无法定位问题
print("发生错误")
正例:
try:
10 / 0
except ZeroDivisionError as e: # 精准捕获,明确问题类型
print(f"除零错误:{e}")
禁用空except分支不允许
except:后无任何处理逻辑,否则错误会被隐藏,调试时无法定位问题。若暂时无法处理,需打印异常信息(print(e))或记录日志。使用finally释放资源文件、数据库连接、网络连接等资源,必须在
finally中关闭,确保无论是否发生异常,资源都会被释放(避免资源泄露)。示例:
file = None
try:
file = open("data.txt", "r")
data = file.read()
except FileNotFoundError as e:
print(f"文件未找到:{e}")
finally:
if file: # 判断文件是否成功打开
file.close() # 确保文件关闭
异常描述清晰,附带上下文信息抛异常时添加具体描述(如
raise ValueError("年龄需在0-150岁之间,当前为200")),便于调试时快速定位问题;捕获异常时可记录上下文(如用户ID、参数值)。自定义异常适配业务场景复杂业务中(如支付、登录、数据校验),使用自定义异常类区分通用异常和业务异常(如
PasswordError、PaymentFailedError),提升代码可读性和错误处理效率。保留异常链,便于调试重新抛出异常时使用
raise 新异常 from 原异常,保留原始异常的堆栈信息,便于追踪异常根源。示例:
try:
check_password("12345")
except PasswordError as e:
# 保留原始异常链,调试时可查看完整堆栈
raise LoginError("zhangsan", "登录失败") from e
- 避免在异常处理中忽略严重错误系统级异常(如
MemoryError、KeyboardInterrupt)通常需要终止程序或向上传递,不应随意捕获并忽略。
五、异常相关核心函数/语句列表(宽表版)
| 函数/语句(黑体) | 参数 | 功能描述 | 备注 |
|---|---|---|---|
| try | 无显式参数 | 定义待监控的异常代码块,是异常捕获的起始标识 | 必须与except、finally或else配合使用,不可单独存在 |
| except | 异常类型(如ZeroDivisionError、ValueError) |
指定要捕获的具体异常类型,精准匹配异常场景 | 可同时捕获多个异常(用元组包裹:except (TypeError, ValueError):) |
| except | as e | 将捕获的异常对象赋值给变量e,用于获取错误详情(如描述、堆栈) |
可选参数,e是异常实例,包含args(异常参数)、with_traceback()(堆栈信息)等属性 |
| else | 无显式参数 | 仅当try块无任何异常时执行,处理正常业务逻辑 |
可选分支,必须位于所有except之后、finally之前 |
| finally | 无显式参数 | 无论try块是否触发异常,均执行该分支 |
核心用于释放资源(文件、连接等),可选分支,可单独与try配合(无except) |
| raise | 异常类型(如ValueError、自定义异常类PasswordError) |
手动触发指定类型的异常,主动中断当前程序逻辑 | 必选核心参数,异常类型必须是Exception的子类 |
| raise | "异常描述字符串" | 为抛出的异常添加自定义描述信息,提升错误可读性 | 可选参数,会作为异常对象的args[0]属性存储 |
| raise | 异常实例(如ValueError("无效值")) |
直接抛出已创建的异常实例 | 适用于需要复用异常对象的场景 |
| raise | from 原异常实例 | 保留异常链,将当前异常与原异常关联 | 用于重新抛出异常时,保留原始异常堆栈(Python 3.3+支持) |
| class(自定义异常) | Exception(父类) | 定义自定义异常类,必须继承Exception基类 |
所有内置异常均继承自Exception,不可继承BaseException(包含系统级异常) |
| init(自定义异常) | self | 异常类的构造方法必传参数,指向当前实例 | 所有类的构造方法都必须包含self参数,不可省略 |
| init(自定义异常) | msg(自定义描述参数) | 接收自定义异常的描述信息,存储到实例属性 | 可选参数,可根据业务需求添加多个参数(如username、error_code) |
| str(自定义异常) | self | 重写该方法,自定义异常打印时的字符串格式 | 可选方法,不重写则默认返回异常类名和args参数 |
| isinstance | 异常对象(如e) |
判断目标对象是否为指定异常类型的实例 | 用于动态校验异常类型(如多异常捕获后的分支处理) |
| isinstance | 异常类型(如ValueError、TypeError) |
指定要校验的目标异常类型 | 支持校验多个类型(用元组包裹:isinstance(e, (TypeError, ValueError))) |
| Exception | 无显式参数 | 所有内置异常的基类,可捕获全部内置异常 | 仅在特殊场景使用(如全局异常捕获),慎用(可能掩盖未知错误) |
| traceback.print_exc() | 无显式参数 | 打印异常的完整堆栈信息(包含文件名、行号、错误链路) | 需导入traceback模块,用于调试时定位异常根源 |
| traceback.format_exc() | 无显式参数 | 将异常堆栈信息转换为字符串,便于日志记录 | 适用于生产环境,避免直接打印堆栈到控制台 |
六、补充说明
异常与错误的区别:语法错误是编译期错误,无法通过代码处理,必须修正语法;异常是运行时错误,可通过
try-except捕获处理,程序可继续执行。Python异常的继承关系:所有异常的顶层父类是
BaseException,Exception是所有内置业务异常的父类(SystemExit、KeyboardInterrupt等系统异常直接继承BaseException,通常不捕获)。日志记录异常:生产环境中,应使用
logging模块记录异常(而非print),包含时间、上下文、堆栈信息,便于问题排查。示例:import logging logging.basicConfig(filename="error.log", level=logging.ERROR) try: 10 / 0 except ZeroDivisionError as e: logging.error(f"除零错误:{e}", exc_info=True) # exc_info=True记录堆栈信息
文档说明:本文档基于Python 3.x版本编写,涵盖错误和异常的核心概念、语法、常见场景、最佳实践及相关API,适用于编程初学者系统学习Python异常处理机制,可作为开发中的参考手册。


