12.1 什么是异常?
在程序运行过程中,难免会出现各种错误。例如,试图打开一个不存在的文件、除以零、访问列表中不存在的索引等,这些错误在 Python 中被称为异常(Exception)。
当程序发生异常时,如果没有进行处理,程序会终止运行并抛出错误信息。例如:
# 除以零,会引发ZeroDivisionError异常
print(10 / 0)
运行结果:
Traceback (most recent call last):
File "test.py", line 1, in <module>
print(10 / 0)
ZeroDivisionError: division by zero
异常处理的目的是捕获程序运行中可能出现的错误,并进行相应的处理(如提示用户、记录日志等),而不是让程序直接崩溃,从而提高程序的健壮性。
12.2 异常处理的基本语法(try-except)
Python 使用try-except语句来处理异常,基本语法如下:
try:
# 可能发生异常的代码块
except 异常类型:
# 当发生指定类型的异常时,执行的代码块
执行流程:
- 执行try块中的代码。
- 如果try块中没有发生异常,则跳过except块,继续执行后续代码。
- 如果try块中发生了指定类型的异常,则立即停止执行try块,转而执行except块中的代码。
- 如果发生的异常类型与except指定的类型不匹配,则程序仍然会终止并抛出错误。
示例:处理除以零的异常
try:
num1 = 10
num2 = 0
result = num1 / num2 # 此处会发生ZeroDivisionError异常
print("计算结果:", result) # 发生异常后,这句代码不会执行
except ZeroDivisionError:
print("错误:除数不能为零!")
print("程序继续执行...") # 异常被处理后,程序会继续执行这句
运行结果:
错误:除数不能为零!
程序继续执行...
12.3 捕获多种异常
一个try块中可能发生多种类型的异常,可以使用多个except块分别处理不同的异常,也可以在一个except块中处理多种异常。
12.3.1 多个 except 块
try:
# 可能发生多种异常的代码
num = int(input("请输入一个整数:"))
result = 10 / num
print("结果:", result)
except ValueError:
print("错误:输入的不是有效的整数!")
except ZeroDivisionError:
print("错误:除数不能为零!")
运行示例 1(输入非整数):
请输入一个整数:abc
错误:输入的不是有效的整数!
运行示例 2(输入 0):
请输入一个整数:0
错误:除数不能为零!
运行示例 3(输入 5):
请输入一个整数:5
结果:2.0
12.3.2 一个 except 块处理多种异常
可以将多种异常类型放在一个元组中,用一个except块处理:
try:
num = int(input("请输入一个整数:"))
result = 10 / num
print("结果:", result)
except (ValueError, ZeroDivisionError):
print("错误:输入无效或除数为零!")
这种方式适合对多种异常进行相同处理的场景,但无法区分具体是哪种异常。
12.4 获取异常信息
在处理异常时,有时需要获取异常的详细信息(如错误原因),可以使用as关键字将异常对象赋值给一个变量。
try:
10 / 0
except ZeroDivisionError as e:
print("发生异常:", e) # 打印异常信息
print("异常类型:", type(e)) # 打印异常类型
运行结果:
发生异常:division by zero
异常类型:<class 'ZeroDivisionError'>
示例:处理文件打开异常并获取信息
try:
with open("nonexistent.txt", "r") as file:
content = file.read()
except FileNotFoundError as e:
print(f"文件操作错误:{e}")
运行结果:
文件操作错误:[Errno 2] No such file or directory: 'nonexistent.txt'
12.5 else 子句
try-except语句可以搭配else子句,else块中的代码会在try块没有发生异常时执行。
try:
num = int(input("请输入一个正数:"))
if num <= 0:
raise ValueError("输入的数不是正数") # 主动抛出异常(后面会讲解)
except ValueError as e:
print("错误:", e)
else:
# 只有当try块没有异常时,才会执行else块
print(f"你输入的正数是:{num}")
运行示例 1(输入负数):
请输入一个正数:-5
错误:输入的数不是正数
运行示例 2(输入正数):
请输入一个正数:10
你输入的正数是:10
else子句的作用是将 “可能发生异常的代码” 和 “正常执行的代码” 分开,使程序结构更清晰。
12.6 finally 子句
try-except语句还可以搭配finally子句,finally块中的代码无论是否发生异常都会执行,通常用于释放资源(如关闭文件、断开数据库连接等)。
try:
file = open("test.txt", "r")
content = file.read()
print("文件内容:", content)
except FileNotFoundError:
print("错误:文件不存在")
finally:
# 无论是否发生异常,都会执行finally块,确保文件关闭
if 'file' in locals() and not file.closed:
file.close()
print("文件已关闭")
运行示例 1(文件存在):
文件内容:Hello, World!
文件已关闭
运行示例 2(文件不存在):
错误:文件不存在
文件已关闭
在实际开发中,finally常用来处理必须执行的清理操作,即使程序发生异常也不会遗漏。
12.7 主动抛出异常(raise 语句)
除了处理程序运行中自动产生的异常,还可以使用raise语句主动抛出异常,用于在满足特定条件时告知程序发生了错误。
语法如下:
raise 异常类型(异常信息)
示例:判断年龄是否合法
def check_age(age):
if not isinstance(age, int):
# 主动抛出TypeError异常
raise TypeError("年龄必须是整数")
if age < 0 or age > 150:
# 主动抛出ValueError异常
raise ValueError("年龄必须在0到150之间")
print(f"年龄{age}是合法的")
try:
check_age(200)
except (TypeError, ValueError) as e:
print("错误:", e)
运行结果:
错误:年龄必须在0到150之间
主动抛出异常可以使函数或方法的错误处理更明确,让调用者知道需要处理哪些可能的错误。
12.8 自定义异常
Python 允许定义自己的异常类,即自定义异常,用于表示特定业务场景下的错误。自定义异常需要继承自Exception类(或其子类)。
示例:定义一个表示余额不足的异常
# 自定义异常类,继承自Exception
class InsufficientFundsError(Exception):
"""表示账户余额不足的异常"""
def __init__(self, balance, amount):
self.balance = balance # 当前余额
self.amount = amount # 尝试支出的金额
super().__init__(f"余额不足:当前余额{balance},尝试支出{amount}")
# 使用自定义异常
def withdraw(balance, amount):
if amount > balance:
# 抛出自定义异常
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
new_balance = withdraw(100, 150)
print(f"取款成功,新余额:{new_balance}")
except InsufficientFundsError as e:
print("取款失败:", e)
运行结果:
取款失败:余额不足:当前余额100,尝试支出150
自定义异常可以使异常信息更贴合具体业务,提高代码的可读性和可维护性。
12.9 示例:异常处理的综合应用
实现一个简易的计算器,支持加、减、乘、除运算,并处理各种可能的异常(如输入非数字、除数为零等)。
def calculator():
print("简易计算器(支持 + - * /)")
try:
# 获取用户输入并转换为数字
num1 = float(input("请输入第一个数字:"))
operator = input("请输入运算符(+ - * /):")
num2 = float(input("请输入第二个数字:"))
# 执行计算
if operator == "+":
result = num1 + num2
elif operator == "-":
result = num1 - num2
elif operator == "*":
result = num1 * num2
elif operator == "/":
if num2 == 0:
# 主动抛出除数为零的异常
raise ZeroDivisionError("除数不能为零")
result = num1 / num2
else:
# 主动抛出不支持的运算符异常
raise ValueError(f"不支持的运算符:{operator}")
print(f"结果:{num1} {operator} {num2} = {result}")
except ValueError as e:
print(f"输入错误:{e}")
except ZeroDivisionError as e:
print(f"计算错误:{e}")
except Exception as e:
# 捕获其他未预料到的异常
print(f"发生未知错误:{e}")
else:
print("计算完成!")
finally:
print("计算器会话结束。\n")
# 运行计算器
calculator()
运行示例 1(正常计算):
简易计算器(支持 + - * /)
请输入第一个数字:10
请输入运算符(+ - * /):*
请输入第二个数字:5
结果:10.0 * 5.0 = 50.0
计算完成!
计算器会话结束。
运行示例 2(输入非数字):
简易计算器(支持 + - * /)
请输入第一个数字:abc
输入错误:could not convert string to float: 'abc'
计算器会话结束。
运行示例 3(除数为零):
简易计算器(支持 + - * /)
请输入第一个数字:10
请输入运算符(+ - * /):/
请输入第二个数字:0
计算错误:除数不能为零
计算器会话结束。
12.10 小结
本章我们学习了 Python 中的异常处理,包括异常的概念、try-except语句的基本用法、捕获多种异常、获取异常信息,以及else和finally子句的作用。此外,还学习了如何使用raise语句主动抛出异常和定义自定义异常。
异常处理是编写健壮程序的关键技术,它可以帮助我们捕获和处理程序运行中可能出现的错误,避免程序意外崩溃。在实际开发中,应根据具体场景合理使用异常处理,既不能忽略可能的异常,也不要过度捕获异常(掩盖真正的错误)。
下一章,我们将学习 Python 中的模块和包,了解如何组织和重用代码。