第10章 错误和异常

第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,会同时捕获IndexErrorKeyError
  • 捕获顺序:先子类,后父类:Python 按except书写顺序匹配异常,若父类写在子类前面,子类异常会被父类 “拦截”,导致精准捕获失效:
  • 自定义异常必须继承 Exception(而非 BaseException)

常见的部分异常类型

异常子类异常子类异常说明
BaseException(根)SystemExitsys.exit () 触发,程序正常退出,不建议捕获
KeyboardInterruptCtrl+C 触发,用户主动中断,不建议捕获
Exception所有业务级异常的核心父类,开发中 99% 的场景捕获此类及其子类
SyntaxError编译时语法错误(非运行时异常)
IndentationError缩进错误
ArithmeticError算术运算通用异常,封装各类数值计算错误
ZeroDivisionError除法 / 取模运算除数为 0,最常用的算术异常
OverflowError数值运算结果超出可表示范围(主要浮点数)
LookupError「查找失败」通用父类,统一序列 / 字典的查找异常,便于批量捕获
IndexError列表 / 元组 / 字符串等序列索引越界
KeyError字典访问不存在的键
OSErrorPython3.3 + 整合 IOError,成为系统 / IO 通用异常,覆盖文件 / 系统相关错误
FileNotFoundError只读模式打开不存在的文件 / 路径
PermissionError对文件执行无权限操作(读 / 写 / 执行)
IsADirectoryError对目录执行文件操作(如 open 一个文件夹)
ImportError导入相关错误,Python3.6 + 新增子类 ModuleNotFoundError
NameError使用未定义的变量 / 函数 / 类,子类 UnboundLocalError(局部变量未绑定)
AttributeError访问对象不存在的属性 / 方法,或修改不可变对象属性
TypeError类型不匹配(如数字 + 字符串相加)、函数传参类型错误
ValueError类型正确但值非法(如 int (“abc”)),子类 UnicodeError(编码 / 解码错误)
StopIteration迭代器遍历完所有元素后,继续调用next()
AssertionErrorassert 语句条件为 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)

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部