第10章 错误和异常
10.1 错误和异常的介绍
Python是一门解释型语言,只有在程序运行后才会执行语法检查。所以,只有在运行或测试程序时,才会真正知道该程序能不能正常运行。
Python有两种错误很容易辨认:语法错误和异常。
10.1.1 编译时语法错误
程序编译时遇到的语法错误。
"""
演示语法错误
"""
try:
print(
except Exception as e:
print(e)
print("其他代码")
以上代码有编译时语法错误:
File "D:shangguiguPythonProjectchapter10p01_syntax_error.py", line 4
print(
^
SyntaxError: '(' was never closed
10.1.2 异常
Python 程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。
大多数的异常都不会被程序处理,都以错误信息的形式打印出来,错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。
"""
演示异常
"""
result = 3 / 0
print(result)
以上代码语法正确,但是运行报错:
Traceback (most recent call last):
File "D:shangguiguPythonProjectchapter10p02_exception_error.py", line 4, in <module>
result = 3 / 0
~~^~~
ZeroDivisionError: division by zero(除数为0的异常)
运行时语法错误:
try:
print(eval("abs(-5)"))
# print(eval("import mathnprint(math.sqrt(9))"))
exec("import mathnprint(math.sqrt(9))")
exec("def add(a,b): return a+bnprint(add(5))")
except Exception as e:
print(e)
print("其他代码")
10.2 异常处理
对异常进行处理并不是将错误规避了,而是当程序运行的时候,出现错误的时候提供解决方案,不终止程序,可以让程序继续执行。
10.2.1 try except
可以使用 try except 语句来捕获异常并处理。
语法格式:
try:
可能发生异常的代码
except 异常类型1:
异常处理的代码
except 异常类型2:
异常处理的代码
except(异常类型3, 异常类型4, 异常类型5):
异常处理的代码
except:
异常处理的代码
- 如果try没有发生异常,程序会忽略except中的代码,继续向下执行。
- 如果try发生了异常,会忽略try中剩余代码,根据异常类型匹配到相应的 except 并执行其中的代码。
- 如果try发生了异常,且异常类型无法和任何except匹配,也没有独立的
except:分支,异常将向外传递。 - 一个except可以同时处理多个异常,将这些异常放在一个元组中。
- 最后一个 except 可以忽略异常类型,它将被作为通配符使用。
演示案例
"""
异常处理
"""
while True:
try:
a = int(input("请输入被除数:"))
b = int(input("请输入除数:"))
print(f"{a} / {b} = {a/b}")
break
except (ValueError, TypeError):
print("必须输入整数")
except ZeroDivisionError:
print("除数不能为0")
except:
print("未知错误" )
print("End")
10.2.2 获取异常描述信息
语法格式:
try:
可能发生异常的代码
except 异常类型1 as 变量名1:
异常处理的代码
except 异常类型2 as 变量名2:
异常处理的代码
except(异常类型3, 异常类型4, 异常类型5) as 变量名3:
异常处理的代码
except:
异常处理的代码
- except的异常类型后面加as 变量名,当发生这个类型的异常时,异常对象就会被赋值给这个变量。单独的except后面不能加变量名。
- 打印
traceback.print_exc()可以打印异常的详细信息,traceback.format_exc()可以返回异常的详细信息,异常对象.__class__获取异常的类型名,异常对象.__str__()返回创建异常对象时传入的描述信息,即异常类型:后面的内容。
示例代码:
"""
异常处理
"""
import traceback
while True:
try:
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
print(f"{a} / {b} = {a/b}")
break
except ValueError as e:
# traceback.print_exc() # 打印异常的详细信息
print("输入错误,请重新输入。异常信息是:",e)
except ZeroDivisionError as e:
print("除数不能为0。异常信息是:", e)
except:
print("未知错误")
print("End")
10.2.3 else
可选地将else放在所有except之后。如果try中代码没有发生异常,将执行 else 中的代码。
语法格式:
try:
可能发生异常的代码
except 异常类型1 【as 变量名1】: #【as 变量名1】表示可选的
异常处理的代码
except 异常类型2 【as 变量名2】:
异常处理的代码
except(异常类型3, 异常类型4, 异常类型5) 【as 变量名3】:
异常处理的代码
except:
异常处理的代码
else:
try中没有异常且没有return语句才执行的代码
- 从执行效果上说,将代码放到else块和直接放到try块中是
一样的。 - 将try正常执行完毕而没有引发任何异常后被执行的代码放到else中。提供了一种清晰的逻辑区分,将正常情况的代码与异常处理代码分开,使代码更易于理解和维护,有助于代码的可读性和可维护性。
示例代码:
"""
异常处理与else
"""
while True:
try:
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
result = a / b
except ValueError:
print("输入错误,请重新输入")
except ZeroDivisionError:
print("除数不能为0")
except:
print("未知错误" )
else:
print(f"{a} / {b} = {result}")
break
print("End")
10.2.4 finally
可选地,放在最后。无论是否发生异常都会执行的代码,通常用于执行一些必须要进行的清理操作,例如关闭文件、释放资源(如网络连接、数据库连接、锁等),即使在执行 try 块中的代码时出现了异常,也能保证这些操作得以完成。
语法格式:
try:
可能发生异常的代码
except 异常类型1 【as 变量名1】: #【as 变量名1】表示可选的
异常处理的代码
except 异常类型2 【as 变量名2】:
异常处理的代码
except(异常类型3, 异常类型4, 异常类型5) 【as 变量名3】:
异常处理的代码
except:
异常处理的代码
else:
try中没有异常才执行的代码
finally:
无论是否发生异常,哪怕有return语句都会执行的代码
- finally 语句块是 try-except 结构的一部分,它确保了无论 try 块中是否发生异常,也不管 except 块是否可以捕获异常,其中的代码都会被执行。
- 如果从执行效果上说,大部分场景,将代码放到finally语句和放到try-except块外效果是一样的。
- 但是直接放在try-except结构外面的代码只会在try-except结构正常执行完毕后才会执行,如果在try块中出现异常且没有被except块捕获,那么这部分代码将不会被执行。
示例代码:
"""
演示finally块的执行特点
"""
try:
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
result = a / b
except ValueError:
print("输入错误,请重新输入")
else:
print(f"{a} / {b} = {result}")
finally:
print("finally")
print("End")
10.3 抛出异常
10.3.1 raise
当你想要在代码中明确表示发生了错误或异常情况时,可以使用 raise 来抛出异常。这可以帮助你在满足某些条件时停止程序的正常执行,并将控制权转移到异常处理部分。
语法格式:
raise 异常类型("异常描述")
案例演示:
# 定义Account类
class Account:
def __init__(self, balance):
self.__balance = balance
def withdraw(self, amount):
if amount < 0:
raise ValueError("取款金额不能为负数")
if amount > self.__balance:
raise ValueError("余额不足")
self.__balance -= amount
def deposit(self, amount):
if amount < 0:
raise ValueError("存款金额不能为负数")
self.__balance += amount
@property
def balance(self):
return self.__balance
# 测试
account = Account(1000)
try:
account.withdraw(-500)
except ValueError as e:
print("取款失败,", e)
try:
account.deposit(-500)
except ValueError as e:
print("存款失败,", e)
try:
account.withdraw(1500)
except ValueError as e:
print("取款失败,", e)
try:
account.withdraw(500)
print("取款成功,余额为:", account.balance)
except ValueError as e:
print("取款失败,", e)
10.3.2 assert
assert用于判断一个表达式,在表达式条件为False的时候触发异常,常用于调试程序。
语法格式:
assert 表达式 [,异常描述]
等价于:
if not 表达式:
raise AssertionError([异常描述])
案例演示:
"""
演示assert断言
"""
# 待测试函数
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# 单元测试(轻量版)
def test_math_functions():
# 验证加法
assert add(2, 3) == 5, "加法函数错误:2+3应等于5"
assert add(-1, 1) == 0, "加法函数错误:-1+1应等于0"
# 验证乘法
assert multiply(2, 3) == 6, "乘法函数错误:2*3应等于6"
assert multiply(0, 5) == 0, "乘法函数错误:0*5应等于0"
print("所有测试用例通过!")
# 执行测试
test_math_functions() # 输出:所有测试用例通过!
# 若函数出错(比如multiply返回a+b),则触发断言:乘法函数错误:2*3应等于6
10.4 异常的传递
当存在 try 嵌套或函数嵌套时,若内层出现了异常且在内层无法处理,会将异常一层一层向外传递,直到异常被处理或程序报错。
# 定义计算三角形面积的函数
def calculate_triangle_area(a, b, c):
"""计算三角形面积的函数"""
if a + b <= c or a + c <= b or b + c <= a or a <= 0 or b <= 0 or c <= 0:
raise ValueError("输入的数字无法构成三角形")
# 使用海伦公式计算面积
p = (a + b + c) / 2
area = (p * (p - a) * (p - b) * (p - c)) ** 0.5
return area
# 处理三角形的函数
def process_triangle():
"""处理三角形的函数"""
try:
a = int(input("请输入边长a的值:"))
b = int(input("请输入边长b的值:"))
c = int(input("请输入边长c的值:"))
# 调用计算面积函数
area = calculate_triangle_area(a, b, c)
print(f"三角形的面积为:{area}")
except ValueError:
print("输入错误,请重新输入")
raise # 重新抛出异常给上层
# 测试代码
while True:
try:
process_triangle()
break
except ValueError as e:
print(e)
10.5 with
Python中的with语句用于异常处理,封装了try except finally编码范式,提供了一种简洁的方式来确保资源的正确获取和释放,同时处理可能发生的异常,提高了易用性。使代码更清晰、更具可读性,简化了文件流等公共资源的管理。
10.5.1 语法格式
语法格式:
with expression as variable:
# 代码块
- expression:通常是一个对象或函数调用,该对象需要是一个上下文管理器,即实现了
__enter__和__exit__方法。 - variable:是可选的,用于存储expression的
__enter__方法的返回值。
10.5.2 示例代码
1、未正确关闭资源
# 定义函数:未在finally中关闭文件
def not_close_in_finally():
file = None
try:
# file = open("test.txt", "r")
file = open("test.txt", "w")
file.write('atguigu')
file.close()
print("文件写入成功")
except OSError as e:
print("文件写入失败:",e)
finally:
if file:
print("文件是否关闭:", file.closed) # 文件是否关闭: False
else:
print("文件未打开")
# 调用测试
not_close_in_finally()
2、在finally中正确关闭文件
# 定义一个函数,处理文件
def use_finally():
file = None
try:
# file = open("test.txt", "r")
file = open("test.txt", "w")
file.write('atguigu')
print("文件写入成功")
except OSError as e:
print("文件写入失败:",e)
finally:
if file:
file.close()
print("文件已关闭")
print("文件是否关闭:", file.closed) # 文件是否关闭: False
else:
print("文件未打开")
# 测试
use_finally()
3、使用with关键字自动关闭文件
# 定义一个函数,处理文件
def use_with():
try:
# with open("test.txt", "w") as file:
with open("test.txt", "r") as file:
file.write('atguigu')
print("文件写入成功")
finally:
print("文件是否关闭:", file.closed) # 文件是否关闭: False
# 测试
use_with()
10.5.3 工作原理
- 当执行with语句时,会自动调用expression对象的
__enter__方法。__enter__方法的返回值可以被存储在 variable 中(如果有),以供 with 代码块中使用。 - 然后执行 with 语句内部的代码块。
- 无论在代码块中是否发生异常,都会自动调用 expression 对象的
__exit__方法,以确保资源的释放或清理工作,这类似于 try-except-finally 中的 finally 子句。
close方法与__exit__方法的区别
close:是文件对象的普通方法,用于显式关闭文件,需要手动调用。如果在 close() 之前发生异常,文件可能不会被关闭。__exit__:是上下文管理器协议的特殊方法,在 with 语句结束时自动调用,由 Python 解释器自动调用。即使发生异常,也会确保执行清理操作。
10.6 异常类型
Python 所有异常类的顶层根类是 BaseException,所有异常都直接 / 间接继承它,核心分支分「系统级异常」和「业务级异常」,开发中常用的Exception是业务异常的父类。Python 异常继承的 3 个关键规则:
- 捕获父类 = 捕获所有子类:捕获某个异常类时,会自动捕获其所有直接 / 间接子类,比如捕获
LookupError,会同时捕获IndexError和KeyError: - 捕获顺序:先子类,后父类:Python 按
except的书写顺序匹配异常,若父类写在子类前面,子类异常会被父类 “拦截”,导致精准捕获失效: - 自定义异常必须继承 Exception(而非 BaseException)
常见的部分异常类型
| 异常 | 子类异常 | 子类异常 | 说明 | |
|---|---|---|---|---|
| BaseException(根) | SystemExit | sys.exit () 触发,程序正常退出,不建议捕获 | ||
| KeyboardInterrupt | Ctrl+C 触发,用户主动中断,不建议捕获 | |||
| Exception | 所有业务级异常的核心父类,开发中 99% 的场景捕获此类及其子类 | |||
| SyntaxError | 编译时语法错误(非运行时异常) | |||
| IndentationError | 缩进错误 | |||
| ArithmeticError | 算术运算通用异常,封装各类数值计算错误 | |||
| ZeroDivisionError | 除法 / 取模运算除数为 0,最常用的算术异常 | |||
| OverflowError | 数值运算结果超出可表示范围(主要浮点数) | |||
| LookupError | 「查找失败」通用父类,统一序列 / 字典的查找异常,便于批量捕获 | |||
| IndexError | 列表 / 元组 / 字符串等序列索引越界 | |||
| KeyError | 字典访问不存在的键 | |||
| OSError | Python3.3 + 整合 IOError,成为系统 / IO 通用异常,覆盖文件 / 系统相关错误 | |||
| FileNotFoundError | 只读模式打开不存在的文件 / 路径 | |||
| PermissionError | 对文件执行无权限操作(读 / 写 / 执行) | |||
| IsADirectoryError | 对目录执行文件操作(如 open 一个文件夹) | |||
| ImportError | 导入相关错误,Python3.6 + 新增子类 ModuleNotFoundError | |||
| NameError | 使用未定义的变量 / 函数 / 类,子类 UnboundLocalError(局部变量未绑定) | |||
| AttributeError | 访问对象不存在的属性 / 方法,或修改不可变对象属性 | |||
| TypeError | 类型不匹配(如数字 + 字符串相加)、函数传参类型错误 | |||
| ValueError | 类型正确但值非法(如 int (“abc”)),子类 UnicodeError(编码 / 解码错误) | |||
| StopIteration | 迭代器遍历完所有元素后,继续调用next() | |||
| AssertionError | assert 语句条件为 False,断言验证失败(调试用) |
10.7 自定义异常
通过直接或者间接继承Exception类来创建自己的异常。例如:
"""
自定义异常
"""
# 定义异常类
class NotATriangleError(Exception):
"""自定义异常类"""
pass
# 测试
while True:
try:
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
c = int(input("请输入第三个数字:"))
if a + b <= c or a + c <= b or b + c <= a or a<=0 or b<=0 or c<=0:
raise NotATriangleError("输入的数字无法构成三角形")
print("三角形的周长为:", a + b + c)
break
except ValueError as e:
print("输入错误,请重新输入整数值:",e)
except NotATriangleError as e:
print(e)