上一篇咱们掌握了文件操作与数据持久化的核心技能,通过文本、JSON文件读写,让学生信息管理系统具备了长期存储数据的"记忆"能力,还优化了系统的实用性与易用性。但在实际运行中,程序难免会遇到意外:读取的文件被误删、用户输入了非法数据、权限不足无法写入文件等,这些场景都会导致程序抛出错误并直接崩溃,既影响用户体验,还可能造成数据丢失。想要让程序具备"容错能力",在异常场景下也能优雅运行、合理提示,就必须掌握异常处理机制------这是构建健壮程序的核心环节。
今天咱们聚焦思维导图"异常处理"核心模块,从异常的本质与分类、try-except核心语法、else-finally补充机制,到主动抛异常、自定义异常,再到实战中优化学生信息管理系统,逐一深入拆解。学会这些内容,你将能精准捕获并处理程序运行中的各类报错,让程序从"一错就崩"升级为"遇错不慌",同时为下一章"模块与包"(实现代码模块化、可复用)打下稳定基础。
一、异常基础:理解程序报错的本质
在Python中,异常是程序运行时发生的错误(如语法正确但逻辑异常),当异常发生且未被处理时,Python解释器会打印异常信息(Traceback),并终止程序执行。异常并非洪水猛兽,它是程序对"意外场景"的提醒,而异常处理就是告诉程序"遇到这类意外该怎么做"。
1. 异常与语法错误的区别
很多初学者会混淆"异常"与"语法错误",二者本质不同,处理方式也完全不一样:
-
语法错误(SyntaxError):代码编写不符合Python语法规则,程序在执行前就会报错,无法启动。例如少写冒号、缩进错误、引号不闭合等,属于"编写阶段错误",必须修正代码才能运行。
-
异常(Exception):代码语法正确,程序可正常启动,但运行中遇到意外场景导致报错。例如读取不存在的文件、除数为0、字符串转数字失败等,属于"运行阶段错误",可通过异常处理机制捕获并处理。
python
# 示例1:语法错误(缩进错误),程序无法启动
def test():
print("语法错误") # 报错:IndentationError: expected an indented block
# 示例2:异常(运行时错误),程序启动后报错
def divide(a, b):
return a / b
divide(10, 0) # 运行时报错:ZeroDivisionError: division by zero
2. Python常见内置异常(实战高频)
Python内置了大量异常类,覆盖各类运行时错误场景,以下是实战中最常遇到的10种异常,需牢记其含义与触发场景,以便精准捕获:
| 异常类型 | 核心含义 | 触发场景示例 |
|---|---|---|
| FileNotFoundError | 文件不存在 | open("不存在的文件.txt", 'r') |
| ZeroDivisionError | 除数为0 | 10 / 0、5 // 0 |
| ValueError | 值错误(类型正确但值非法) | int("abc")、float("xyz") |
| TypeError | 类型错误(操作对象类型不匹配) | "10" + 5、len(123) |
| KeyError | 字典键不存在 | student = {"name":"小明"}; student["age"] |
| IndexError | 列表/元组索引越界 | lst = [1,2,3]; lst[5] |
| PermissionError | 权限不足 | 写入受保护的系统文件、删除无权限的文件 |
| UnicodeDecodeError | 编码解码错误 | 用gbk编码读取utf-8格式的文件 |
| JSONDecodeError | JSON格式非法 | json.load()读取格式错误的JSON文件 |
| AttributeError | 对象属性/方法不存在 | student = Student(); student.grade(无grade属性) |
补充:所有内置异常都继承自BaseException类,其中Exception是最常用的异常父类(除SystemExit、KeyboardInterrupt等退出类异常外,多数异常都继承自它),后续自定义异常也需继承此类。
二、异常处理核心语法:try-except语句
异常处理的核心是`try-except`语句,其逻辑是:将可能抛出异常的代码放入`try`块,当`try`块中代码正常运行时,跳过`except`块;当`try`块中抛出异常时,终止`try`块执行,转而执行对应异常类型的`except`块,从而避免程序崩溃。
1. 基础语法:捕获指定异常
针对已知可能发生的异常,精准捕获对应类型,既能处理异常,又能避免捕获无关错误,便于定位问题。
python
# 语法格式
try:
# 可能抛出异常的代码块
代码逻辑
except 异常类型1:
# 捕获到异常类型1时执行的处理逻辑
异常处理代码
except 异常类型2:
# 捕获到异常类型2时执行的处理逻辑
异常处理代码
# 示例:处理文件读取的FileNotFoundError和UnicodeDecodeError
try:
with open("students.json", 'r', encoding='utf-8') as f:
content = f.read()
print("文件读取成功")
except FileNotFoundError:
print("异常处理:文件不存在,请检查文件路径是否正确")
except UnicodeDecodeError:
print("异常处理:编码错误,请确认文件编码格式为utf-8")
核心优势:精准捕获目标异常,每个异常对应专属处理逻辑,代码可读性与可维护性更强。
2. 多异常合并捕获
当多个异常的处理逻辑相同时,可将其放入一个`except`块,用元组包裹多个异常类型,简化代码。
python
try:
# 可能触发ValueError或TypeError的代码
num1 = int(input("请输入第一个数字:"))
num2 = int(input("请输入第二个数字:"))
result = num1 / num2
print(f"计算结果:{result}")
except (ValueError, TypeError):
# 同时处理"值错误"和"类型错误"
print("异常处理:请输入合法的整数")
except ZeroDivisionError:
print("异常处理:除数不能为0")
3. 捕获所有异常(慎用)
若无法预判可能发生的异常类型,可使用`except Exception:`捕获所有非退出类异常(不推荐轻易使用,易掩盖潜在问题),或用`except:`捕获所有异常(包括退出类异常,更不推荐)。
python
try:
num1 = int(input("请输入数字:"))
num2 = int(input("请输入另一个数字:"))
print(num1 / num2)
except Exception as e:
# as e:获取异常详细信息,便于调试
print(f"程序发生异常:{type(e).__name__},详情:{e}")
注意:捕获所有异常会隐藏代码中的逻辑漏洞(如拼写错误、逻辑错误),仅建议在程序顶层兜底处理,或临时调试时使用,日常开发优先精准捕获指定异常。
4. else语句:无异常时执行
`else`可与`try-except`搭配使用,当`try`块中代码无异常正常执行完毕后,会执行`else`块内容;若发生异常,则跳过`else`块。适用于"异常时处理错误,正常时执行后续逻辑"的场景。
python
try:
num1 = int(input("请输入整数:"))
num2 = int(input("请输入另一个整数:"))
result = num1 / num2
except ValueError:
print("异常处理:请输入合法整数")
except ZeroDivisionError:
print("异常处理:除数不能为0")
else:
# 无异常时执行,输出计算结果
print(f"计算成功,结果为:{result:.2f}")
5. finally语句:无论是否异常都执行
`finally`与`try-except`搭配使用,无论`try`块是否发生异常、`except`块是否执行,`finally`块内容都会强制执行。核心用途是释放资源(如关闭文件、断开数据库连接),即使发生异常也能保障资源正常释放。
python
# 示例:手动打开文件时,用finally确保文件关闭
f = None
try:
f = open("test.txt", 'r', encoding='utf-8')
content = f.read()
print("文件读取内容:", content)
except FileNotFoundError:
print("异常处理:文件不存在")
finally:
# 无论是否异常,都关闭文件
if f is not None:
f.close()
print("文件已关闭,资源释放完成")
补充:之前学的`with`语句本质上是`try-finally`的语法糖,自动帮我们释放资源,无需手动关闭文件,但`finally`在非文件操作场景(如数据库连接)中仍有广泛应用。
6. 完整组合:try-except-else-finally
将四个语句组合使用,可实现完整的异常处理逻辑:尝试执行代码→异常时处理→无异常时执行后续→无论是否异常都释放资源。
python
try:
num1 = int(input("请输入被除数:"))
num2 = int(input("请输入除数:"))
result = num1 / num2
except (ValueError, TypeError):
print("异常处理:请输入合法整数")
except ZeroDivisionError:
print("异常处理:除数不能为0")
else:
print(f"计算结果:{result}")
finally:
print("程序执行完毕(无论是否异常,此语句都会执行)")
三、主动抛出异常:raise语句
除了捕获程序运行时自动抛出的异常,我们还可通过`raise`语句主动抛出异常,用于对自定义规则的校验(如参数合法性、业务逻辑合规性),明确告知程序"当前场景不符合预期,需触发异常处理"。
1. 基础用法:抛出指定异常
用`raise 异常类型()`主动抛出异常,可添加异常信息(字符串),让异常更易理解。
python
def check_age(age):
# 自定义规则:年龄需在0-120之间,否则主动抛异常
if not isinstance(age, int):
raise TypeError("年龄必须是整数类型")
if age < 0 or age > 120:
raise ValueError(f"年龄{age}不合法,需在0-120之间")
print(f"年龄合法:{age}岁")
# 测试主动抛异常
try:
check_age(150)
except (TypeError, ValueError) as e:
print(f"异常处理:{e}") # 输出:异常处理:年龄150不合法,需在0-120之间
try:
check_age("20")
except (TypeError, ValueError) as e:
print(f"异常处理:{e}") # 输出:异常处理:年龄必须是整数类型
2. 重新抛出异常:不掩盖异常
有时需要先捕获异常做一些处理(如记录日志),再将异常重新抛出,让上层代码继续处理。此时用`raise`无参数形式,可保留原异常的类型和详细信息。
python
import logging
def read_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError as e:
# 记录异常日志,再重新抛出异常
logging.error(f"文件读取失败:{e}")
raise # 重新抛出原异常,不改变异常信息
# 上层代码处理重新抛出的异常
try:
content = read_file("不存在的文件.txt")
except FileNotFoundError as e:
print(f"上层处理:{e}")
四、自定义异常:适配业务场景的专属异常
Python内置异常适用于通用场景,当业务有特殊规则时,可自定义异常类(继承自Exception),让异常更贴合业务逻辑,提升代码可读性和可维护性。例如学生成绩范围校验、用户权限校验等场景,都可定义专属异常。
1. 自定义异常基础语法
自定义异常类需继承Exception(不要继承BaseException,避免捕获退出类异常),可自定义初始化方法,添加专属属性和方法。
python
# 自定义异常类(继承Exception)
class ScoreError(Exception):
"""自定义异常:学生成绩不合法(0-100之间)"""
# 自定义初始化方法,添加异常详情
def __init__(self, score, message="成绩不合法,需在0-100之间"):
self.score = score # 异常关联的成绩值
self.message = message
super().__init__(self.message) # 调用父类初始化方法
# 自定义异常描述,便于打印
def __str__(self):
return f"{self.message},当前成绩:{self.score}"
class PermissionError(Exception):
"""自定义异常:用户权限不足"""
def __init__(self, user_role, need_role):
self.user_role = user_role # 当前用户角色
self.need_role = need_role # 所需角色
super().__init__(f"权限不足:当前角色{user_role},需{need_role}角色")
2. 自定义异常的使用的
自定义异常的使用方法与内置异常一致,用`raise`主动抛出,用`try-except`捕获处理。
python
# 测试自定义异常:成绩校验
def check_score(score):
if not isinstance(score, (int, float)):
raise TypeError("成绩必须是数字类型")
if score < 0 or score > 100:
# 主动抛出自定义异常
raise ScoreError(score)
print(f"成绩校验通过:{score}分")
# 测试自定义异常:权限校验
def check_permission(user_role, need_role):
if user_role != need_role:
raise PermissionError(user_role, need_role)
print("权限校验通过,可执行操作")
# 捕获自定义异常
try:
check_score(105)
except (TypeError, ScoreError) as e:
print(f"异常处理:{e}") # 输出:异常处理:成绩不合法,需在0-100之间,当前成绩:105
try:
check_permission("student", "admin")
except PermissionError as e:
print(f"异常处理:{e}") # 输出:异常处理:权限不足:当前角色student,需admin角色
五、实战:给学生信息管理系统添加全面异常处理
结合本章异常处理知识,优化上一章的学生信息管理系统,针对文件操作、用户输入、业务逻辑等场景添加异常处理,解决"文件不存在、JSON格式错误、输入非法数据、权限不足"等问题,让系统在异常场景下也能稳定运行,同时给出友好提示。
python
import json
import logging
# 自定义异常类
class ScoreError(Exception):
"""自定义异常:成绩不合法(0-100之间)"""
def __init__(self, score):
self.score = score
super().__init__(f"成绩{score}不合法,需在0-100之间")
class PermissionError(Exception):
"""自定义异常:用户权限不足"""
def __init__(self, user_role):
super().__init__(f"权限不足:{user_role}无此操作权限")
# 父类:用户类
class User(object):
def __init__(self, name, account, password):
self.name = name
self.account = account
self.__password = password
def __verify_pwd(self, input_pwd):
return self.__password == input_pwd
def login(self, input_pwd):
try:
if not isinstance(input_pwd, str):
raise TypeError("密码必须是字符串类型")
if self.__verify_pwd(input_pwd):
print(f"{self.name}({self.account})登录成功")
return True
else:
raise ValueError("密码错误")
except TypeError as e:
print(f"登录异常:{e}")
return False
except ValueError as e:
print(f"登录异常:{e}")
return False
# 子类:学生类
class Student(User):
def __init__(self, name, account, password, score):
super().__init__(name, account, password)
self.score = score
def check_score(self):
print(f"{self.name}的成绩:{self.score}分")
def to_dict(self):
return {
"name": self.name,
"account": self.account,
"password": self._User__password,
"score": self.score
}
# 子类:管理员类
class Admin(User):
def __init__(self, name, account, password):
super().__init__(name, account, password)
def manage_student(self, manager, action, *args):
try:
if action == "add":
name, age, score = args
# 校验成绩合法性
if not isinstance(score, (int, float)):
raise TypeError("成绩必须是数字类型")
if score < 0 or score > 100:
raise ScoreError(score)
manager.add_student(name, age, score)
elif action == "modify":
name, new_score = args
if not isinstance(new_score, (int, float)):
raise TypeError("成绩必须是数字类型")
if new_score < 0 or new_score > 100:
raise ScoreError(new_score)
manager.modify_student(name, new_score=new_score)
elif action == "delete":
name = args[0]
manager.delete_student(name)
else:
raise ValueError(f"操作类型{action}不合法,仅支持add/modify/delete")
except (TypeError, ScoreError, ValueError) as e:
print(f"管理操作异常:{e}")
# 学生信息管理类(添加全面异常处理)
class StudentManager(object):
def __init__(self, file_path="students.json"):
self.file_path = file_path
self.student_list = self.load_data()
def load_data(self):
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
# 捕获JSON格式错误
try:
data = json.load(f)
except json.JSONDecodeError as e:
logging.error(f"JSON格式错误:{e}")
raise ValueError("数据文件格式非法,无法加载") from e
# 校验数据结构
if not isinstance(data, list):
raise ValueError("数据文件格式错误,需为列表类型")
student_list = []
for item in data:
if not isinstance(item, dict) or not all(key in item for key in ["name", "account", "password", "score"]):
raise ValueError(f"数据项格式错误:{item}")
student = Student(
name=item["name"],
account=item["account"],
password=item["password"],
score=item["score"]
)
student_list.append(student)
print("数据加载成功")
return student_list
except FileNotFoundError:
print("数据文件不存在,初始化空列表")
return []
except PermissionError:
print("权限不足,无法读取数据文件")
return []
except UnicodeDecodeError:
print("编码错误,文件编码需为utf-8")
return []
except ValueError as e:
print(f"数据加载异常:{e}")
return []
except Exception as e:
logging.error(f"未知异常:{e}")
print("数据加载失败,未知异常")
return []
def save_data(self):
try:
data = [student.to_dict() for student in self.student_list]
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("数据保存成功")
except PermissionError:
print("权限不足,无法写入数据文件")
except Exception as e:
logging.error(f"数据保存异常:{e}")
print("数据保存失败,未知异常")
def add_student(self, name, age, score):
student = Student(
name=name,
account=f"stu_{len(self.student_list)+1:03d}",
password="123456",
score=score
)
self.student_list.append(student)
self.save_data()
print(f"成功添加学生:{name},默认账号:{student.account},默认密码:123456")
def query_student(self, name):
try:
if not isinstance(name, str):
raise TypeError("查询姓名必须是字符串类型")
for student in self.student_list:
if student.name == name:
print(f"查询结果:姓名:{student.name},账号:{student.account},成绩:{student.score}")
return student
print(f"未找到姓名为{name}的学生")
return None
except TypeError as e:
print(f"查询异常:{e}")
return None
def modify_student(self, name, new_score=None):
student = self.query_student(name)
if student and new_score:
student.score = new_score
self.save_data()
print(f"成功修改{name}的成绩,当前成绩:{student.score}分")
def delete_student(self, name):
try:
if not isinstance(name, str):
raise TypeError("删除姓名必须是字符串类型")
for i, student in enumerate(self.student_list):
if student.name == name:
del self.student_list[i]
self.save_data()
print(f"成功删除学生:{name}")
return
print(f"未找到姓名为{name}的学生,删除失败")
except TypeError as e:
print(f"删除异常:{e}")
def show_all_students(self):
if not self.student_list:
print("暂无学生信息")
return
print("所有学生信息:")
for i, student in enumerate(self.student_list, 1):
print(f"第{i}名:姓名:{student.name},账号:{student.account},成绩:{student.score}")
# 测试优化后的系统
if __name__ == "__main__":
# 初始化日志配置
logging.basicConfig(filename="error.log", level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s")
manager = StudentManager()
admin = Admin("管理员", "admin", "admin123")
if admin.login("admin123"):
# 测试异常场景:添加成绩非法的学生
admin.manage_student(manager, "add", "小明", 18, 105)
# 测试正常场景:添加合法学生
admin.manage_student(manager, "add", "小明", 18, 90)
admin.manage_student(manager, "add", "小红", 17, 88)
manager.show_all_students()
# 测试异常场景:修改成绩为字符串
admin.manage_student(manager, "modify", "小红", "优秀")
# 测试正常场景:修改成绩
admin.manage_student(manager, "modify", "小红", 92)
manager.show_all_students()
student = manager.query_student("小红")
if student:
student.login("123456")
student.check_score()
案例分析
-
全场景异常覆盖:针对文件操作(读写、编码、权限)、数据校验(成绩、姓名、JSON格式)、用户操作(登录、管理权限)等场景,分别捕获内置异常和自定义异常,避免程序崩溃;
-
友好提示与日志记录:异常发生时,既给用户友好的中文提示,又通过logging记录详细异常信息,便于后续调试和问题定位;
-
自定义异常适配业务:通过ScoreError、PermissionError等自定义异常,精准匹配学生系统的业务规则,让异常逻辑更清晰;
-
代码健壮性提升:即使出现文件损坏、输入非法数据等意外,系统也能正常运行并提示问题,大幅提升用户体验和系统稳定性。
六、异常处理避坑指南
-
避免过度捕获:不滥用`except Exception:`捕获所有异常,优先精准捕获指定异常,防止掩盖代码逻辑漏洞;
-
异常信息明确:捕获异常时,给出具体提示(如"文件不存在,请检查路径"而非"文件错误"),便于用户排查问题;
-
资源释放优先:文件、数据库连接等资源操作,务必用finally或with语句确保资源释放,避免资源泄露;
-
自定义异常继承正确:自定义异常需继承Exception,不要继承BaseException,避免捕获系统退出等核心异常;
-
合理记录日志:异常发生时,除了用户提示,还需记录详细日志(时间、异常类型、详情),便于后续调试。
结尾:异常处理搞定了
今天咱们彻底掌握了异常处理的核心内容,从理解异常本质、捕获内置异常,到主动抛异常、自定义业务异常,再到实战中给学生系统添加全场景异常处理,让程序具备了强大的容错能力,实现了"遇错不崩、优雅提示"的目标。现在你已经能开发出健壮、稳定的Python程序,应对各类意外场景。