Python3 基础教程

Python3 错误和异常

🎉摘要:本文详细介绍 Python3 中的错误与异常处理机制。涵盖错误与异常的区别,try-except、try-except-else、try-except-finally 语句用法,以及如何使用 raise 抛出内置和自定义异常。此外,还讲解了 BaseException 与 Exception 异常 hierarchy,以及使用 IDLE 和 assert 语句进行程序调试的方法,帮助开发者编写健壮稳定的 Python 程序。

程序运行过程中,难免会出现语法错误、逻辑错误、外部数据错误等情况。

在 Python3 中,提供了完善的错误与异常处理机制,让程序不会因为意外问题直接崩溃,同时能记录问题的详细日志,方便排查问题。掌握错误和异常机制是编写健壮、稳定程序的必备能力。

异常概述

什么是错误?

错误就是程序不符合语言规则,或运行时出问题,导致无法正常执行。

例如,代码写法不合法(少冒号、缩进错、关键字拼错),Python 解释器看不懂,运行前直接报错,程序根本跑不起来,必须改对程序才能运行。

示例:缺少冒号,直接报错,无法运行

if True
    print("hello")

运行时抛出如下错误:

  File "/home/cloudlab/main.py", line 1
    if True
           ^
SyntaxError: expected ':'

什么是异常?

异常指代码语法没错,但运行时出现的意外错误,会导致程序中断。比如:

  • 除以 0

  • 列表索引越界

  • 打开不存在的文件

  • 使用没定义的变量

注意,这类错误不是写错语法,而是运行条件出问题,Python 会抛出异常。如果不处理,程序直接崩溃。为了防止程序崩溃,可以使用 try-except 捕获异常,这样程序可以继续运行。

在 Python 中,常见异常如下:

  • ZeroDivisionError:除零错误

  • IndexError:列表索引超出范围

  • KeyError:字典键不存在

  • FileNotFoundError:文件不存在

  • TypeError:类型错误

示例:除 0 异常

print(10 / 0)

运行时抛出如下错误:

Traceback (most recent call last):
  File "/home/cloudlab/main.py", line 1, in <module>
    print(10 / 0)
          ~~~^~~
ZeroDivisionError: division by zero

异常处理语句

在 Python 中,为了更好地应对程序执行过程中可能出现的异常情况,特地提供了一系列专门的语句来进行异常的捕获与处理。详细内容如下:

try...except 语句

try...except 是 Python 中专门用来捕获并处理运行时异常的语句。主要作用如下:

  • 让程序遇到错误不崩溃

  • 对异常进行捕获、记录、处理、恢复

  • 可以提高程序健壮性

语法如下:

try:
    # 可能出错的代码
except 异常类型:
    # 发生异常时执行的代码

示例:处理不能除以 0 的异常

try:
    num = 10 / 0  # 会触发异常
except ZeroDivisionError:
    print("错误:不能除以 0!")

运行结果:

错误:不能除以 0!

上面示例捕获了 ZeroDivisionError 异常,如果程序抛出多个异常呢?可以使用多异常捕获。

多异常捕获指的是在编程过程中,针对可能出现的多种不同类型的异常情况进行捕捉处理的机制。当程序执行时,可能会因为各种原因引发不同类型的异常,比如文件读取失败、数据类型转换错误、网络连接中断等等。通过多异常捕获,程序员能够以一种更为灵活且全面的方式来应对这些潜在的问题。

示例:捕获除零错误和索引越界错误,如下:

try:
    lst = [1, 2]
    print(lst[5])  # IndexError
except ZeroDivisionError:
    print("除零错误")
except IndexError:
    print("索引越界")

运行结果:

索引越界

上述示例,通过两个 except 语句分别捕获了 ZeroDivisionError 和 IndexError 错误。那么,如果程序抛出了很多种类的异常,我们则需要编写很多 except 语句来分别应对各种错误,是不是有点麻烦。庆幸的是,你可以一次性捕获所有的异常。就像下面这样:

try:
    lst = [1, 2]
    print(lst[5])
except:
    print("发生未知错误")

运行结果:

发生未知错误

注意,在编程实践中不被推荐这样做。虽然它看似能全面处理各种可能出现的异常情况,但实际上却存在较大弊端,会隐藏未知错误。当使用捕获所有异常的方式时,程序不管遇到何种异常,都会统一按照既定的处理逻辑执行。

然而,这就导致了在遇到一些未曾预料到的错误时,开发人员无法清晰地了解问题究竟出在哪里。这些未知错误被掩盖,不利于对程序进行调试和优化,可能会在后续引发更为严重的问题,所以在大多数情况下,不建议采用捕获所有异常的方式。

try...except...else 语句

try...except...else 语句是 Python 中用于异常处理的另一种结构。在这种语句结构里,try 代码块和 except 代码块作用不变。特别需要注意的是 else 代码块,它只有在 try 代码块里没有发生任何异常时才会执行。也就是说,一旦 try 代码块顺利执行完毕,没有触发任何异常情况,程序流程便会紧接着进入 else 代码块继续执行其中的代码。

语法如下:

try:
    # 可能出错的代码
except 异常:
    # 异常处理
else:
    # 无异常时执行

示例:

try:
    result = 10 / 2
except ZeroDivisionError:
    print("除零错误")
else:
    print("计算成功,结果:", result)  # 无异常会执行

运行结果:

计算成功,结果: 5.0

如果改成 10 / 0,将抛出 ZeroDivisionError 错误:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("除零错误")
else:
    print("计算成功,结果:", result)  # 无异常会执行

运行结果:

除零错误

try...except...finally 语句

try...except...finally 是 Python 中功能最完整的异常处理结构。其中 try 与 except 语句的作用保持不变,try 用于包裹可能出现异常的代码,except 用于捕获并处理对应的异常。

而 finally 代码块有着特殊的执行机制,无论程序在运行过程中是否发生异常、出现的异常是否被成功处理,其中的代码都会 100% 被执行。

该结构在实际开发中十分常用,通常用于执行收尾与清理操作,比如关闭打开的文件、断开数据库连接、释放程序占用的系统资源等。

语法如下:

try:
    # 可能出错的代码
except:
    # 异常处理
finally:
    # 必须执行的代码

示例:打开 test.txt 文件,使用 read() 读取文件内容,最后在 finally 语句中使用 close() 语句关闭文件句柄,释放资源。

try:
    # 打开文件
    file = open("test.txt", "r", encoding="utf-8")
    # 读取文件内容
    content = file.read()
    print(content)
except Exception as e:
    # 捕获并打印异常信息
    print(f"An error occurred: {e}")
finally:
    # 关闭文件
    file.close()

使用 raise 语句抛出异常

前面介绍了如何使用 try...except 语句捕获和处理异常,除了捕获异常,我们自己还可以手动抛出异常。

在 Python 中,可以通过 raise 语句抛出异常,raise 是 Python 中用于主动抛出异常的关键字,可强制触发内置的异常错误,也可以抛出自定义异常。

抛出异常的核心作用是自定义业务逻辑错误、参数校验、流程中断等场景。

语法如下:

raise 异常类型(提示信息)

示例(内置异常):当年龄小于 0 时,使用 raise 语句抛出 ValueError 内置异常

def check_age(age):
    if age < 0:
        # 主动抛出异常
        raise ValueError("年龄不能为负数!")
    print("年龄合法")

try:
    check_age(-5)
except ValueError as e:
    print("捕获异常:", e)

运行结果:

捕获异常: 年龄不能为负数!

除了抛出内置异常外,raise 还能抛出自定义异常。而自定义异常是继承内置 Exception 类创建的专属异常,专门用于匹配自己的业务规则。

示例(自定义异常):首先定义一个名为 AgeError 的自定义异常,再在业务中通过 raise 语句抛出异常,最后通过 try...except 捕获异常,如下:

# 1. 定义自定义异常(继承 Exception)
class AgeError(Exception):
    """自定义年龄异常:用于年龄不合法的场景"""
    pass

# 2. 业务函数 + raise 抛出自定义异常
def check_age(age):
    if age < 0 or age > 150:
        # 主动抛出自定义异常,附带描述信息
        raise AgeError(f"年龄 {age} 不合法,必须在 0~150 之间")
    return f"年龄 {age} 合法"

# 3. 调用并捕获异常
try:
    check_age(200)
except AgeError as e:
    print(f"捕获到自定义异常:{e}")

运行结果:

捕获到自定义异常:年龄 200 不合法,必须在 0~150 之间

上述示例中,AgeError 异常类没有添加任何逻辑,通过 pass 语句占位。我们可以使用 __init__() 构造方法可以给自定义异常写固定的初始化逻辑,如下:

# 1. 定义自定义异常(继承 Exception)
class AgeError(Exception):
    """自定义年龄异常:用于年龄不合法的场景"""
    # 为 message 指定默认信息
    def __init__(self, message = "年龄不合法"):
        super().__init__(message)  # 调用父类构造函数,传递异常信息

# 2. 业务函数 + raise 抛出自定义异常
def check_age(age):
    if age < 0 or age > 150:
        # 主动抛出自定义异常,附带描述信息
        raise AgeError()
    return f"年龄 {age} 合法"

# 3. 调用并捕获异常
try:
    check_age(200)
except AgeError as e:
    print(f"捕获到自定义异常:{e}")

运行结果:

捕获到自定义异常:年龄不合法

内置异常

你写代码时,程序一旦出错,Python 不会只说 “错了”,而是会抛出一个有名字、有分类、有含义的错误,这些错误就是内置异常。

比如:

  • 除以 0 → ZeroDivisionError

  • 变量没定义 → NameError

  • 列表下标越界 → IndexError

  • 打开不存在的文件 → FileNotFoundError

上面的这些异常都不是我们自己写的,而是 Python 内置在解释器里的,所以叫内置异常。

异常顶级父类:BaseException

在 Python3 中,BaseException 类是所有异常的根类,包含系统异常 + 业务异常。开发者永远不要直接继承 / 捕获它(因为这会拦截 Ctrl+C、系统退出等)。

系统级异常(不建议捕获),如下:

BaseException
├─ SystemExit                # 执行 exit()/sys.exit() 触发,程序正常退出
├─ KeyboardInterrupt         # Ctrl+C 终止程序触发
├─ GeneratorExit             # 生成器关闭时触发
└─ Exception                 # 业务异常父类

业务异常父类:Exception

下面是 Python 中常用的业务异常类,覆盖了 99% 的业务,了解即可,当程序抛出某个异常,能知道异常代表什么错误就可以了,不需要全部记住:

Exception
├─ StopIteration       		        # 迭代器无更多数据时抛出
├─ StopAsyncIteration  		        # 异步迭代器结束
├─ ArithmeticError     		        # 数学计算错误基类
│  ├─ ZeroDivisionError 	        # 除 0 错误
│  ├─ OverflowError     	        # 数值溢出(Python 极少出现)
│  └─ FloatingPointError 	        # 浮点数计算错误
├─ AssertionError      		        # assert 断言失败
├─ AttributeError      		        # 对象无此属性(如 a.b 不存在)
├─ BufferError         		        # 缓冲区操作失败
├─ EOFError            		        # 读取到文件末尾(未读到数据)
├─ ImportError         		        # 模块导入失败
│  └─ ModuleNotFoundError 	        # 模块找不到(ImportError 子类)
├─ LookupError         		        # 查找/索引错误基类
│  ├─ IndexError        	        # 列表索引越界(如 list[999])
│  └─ KeyError          	        # 字典键不存在
├─ MemoryError         		        # 内存不足
├─ NameError           		        # 使用未定义变量
│  └─ UnboundLocalError 	        # 函数内局部变量未赋值先使用
├─ OSError             		        # 操作系统错误基类
│  ├─ FileNotFoundError   	        # 文件不存在
│  ├─ PermissionError     	        # 权限不足
│  ├─ IsADirectoryError   	        # 操作了目录而非文件
│  ├─ NotADirectoryError   	        # 操作了文件而非目录
│  ├─ FileExistsError      	        # 创建已存在文件
│  └─ TimeoutError         	        # 系统操作超时
├─ ReferenceError      		        # 弱引用对象被回收后访问
├─ RuntimeError        		        # 运行时通用错误(不推荐使用)
│  └─ NotImplementedError 	        # 方法未实现
├─ SyntaxError         		        # 语法错误
│  └─ IndentationError 		        # 缩进错误
├─ SystemError         		        # 解释器内部错误(非致命)
├─ TypeError           		        # 类型错误(如 1 + "str")
├─ ValueError          		        # 值合法但无效(如 int("abc"))
│  └─ UnicodeError     		        # 编码/解码错误
│     ├─ UnicodeEncodeError		# 从字节 → 字符串 时出错
│     ├─ UnicodeDecodeError		# 从字符串 → 字节 时出错
│     └─ UnicodeTranslateError	        # 字符串在转换 / 映射过程中出错
└─ Warning             		        # 警告基类(非错误)

最后,关于异常的一些核心规则,需要谨记:

  • 自定义异常必须继承 Exception,不要继承 BaseException

  • 捕获顺序,先捕获子类再捕获父类

  • except Exception 能捕获所有业务错误,但不会捕获 Ctrl+C/ 退出

  • 系统异常(KeyboardInterrupt)不要捕获

程序调试

程序调试,就是:找出代码哪里错了、为什么错,然后把它修好的整个过程。

使用自带的 IDLE 进行程序调试

在 Windows 中搜索 IDLE,运行 IDLE,如下图:

IDLE 是 Python 自带内置的集成开发与学习环境,无需额外安装,即可开始编写 Python 代码。IDLE 提供简洁易用的界面,对编程新手而言是理想的入门工具。它具备代码编辑、运行和调试等基础功能,整个环境简单易懂,非常适合初学编程的人员使用。

调试步骤如下:

(1)打开 IDLE → 新建文件 → 编写代码,如下图:

代码内容如下(将代码保存到桌面 debug.py 文件):

# 计算两个数的和,并判断结果是否大于10
def debug_demo():
    # 1. 定义变量
    a = 5
    b = 3
    
    # 2. 计算
    total = a + b
    print("计算结果:", total)

    # 3. 条件判断
    if total > 10:
        print("结果大于10")
    else:
        print("结果小于等于10")

    # 4. 循环演示
    for i in range(3):
        print("循环次数:", i)

# 调用函数
debug_demo()

(2)点击顶部菜单 Debug → Debugger,如下图:

调试窗口如下图:

此时没有任何程序可以调试。

(3)切换到代码编辑器,选择“Run → Run Module”菜单,如下图:

点击“Run Module”后,将自动运行程序且跳转到 Debugger 调试界面,如下图:

此时,可以使用按钮进行控制:

  • Go:运行到下一个断点

  • Step:单步执行(一行一行走)

  • Over:跳过函数

  • Out:跳出函数

  • Quit:退出调试

(4)最后,切换到代码编辑器,设置断点开始调试。将光标移动到需要的行,然后右击,打开右键菜单,选择“Set Breakpoint(设置断点)”或者“Clear Breakpoint(清空断点)”,如下图:

我们将在“a = 5”行设置断点,设置后的效果如下图:

此时,跳转到 Debugger 选择“Go”按钮,如下图:

下图演示了调试的步骤:

使用 assert 语句调试程序

assert 中文叫断言,是 Python 内置的调试、校验工具。

用来主动检查某个条件必须为真,否则直接抛出 AssertionError 异常,中断程序。该中方式非常适合用来校验参数、检查中间结果、快速定位逻辑错误。

语法如下:

assert 条件, 错误信息

示例:使用 assert 来断言除数不能为零,如果除数为 0,则抛出异常

def divide(a, b):
    # 断言:b 不能为 0
    assert b != 0, "除数不能为 0"
    return a / b

divide(10, 0)  # 触发 AssertionError

运行结果:

Traceback (most recent call last):
  File "/home/cloudlab/main.py", line 6, in <module>
    divide(10, 0)  # 触发 AssertionError
    ^^^^^^^^^^^^^
  File "/home/cloudlab/main.py", line 3, in divide
    assert b != 0, "除数不能为 0"
           ^^^^^^
AssertionError: 除数不能为 0

断言专为开发调试设计,正式运行时可关闭断言,适合快速校验参数与程序状态。在 Python 中,关闭断言非常简单,只需要在运行时加一个 -O(大写字母 O)参数即可,如下:

python -O 你的脚本文件名.py

更多 Python3 知识,请继续学习后续章节。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号