[Python编程] Python3 错误与异常
目录
一、Python3 错误的分类:先搞懂 “错在哪”
1️⃣语法错误:最基础的 “拼写错误”
常见场景与示例
关键特点
2️⃣运行时异常:“代码能跑,但中途出问题”
常见场景与示例
关键特点
二、Python3 常见异常类型:认识 “敌人” 才能应对
补充说明
三、Python3 异常的处理:让程序 “优雅容错”
1️⃣核心语法:try/except 基础用法
基本语法结构
示例 1:捕获特定异常(推荐)
示例 2:捕获多个异常
示例 3:通用异常捕获(谨慎使用)
2️⃣进阶用法:try/except/else
语法结构
示例:文件读取场景
3️⃣必学用法:try/except/finally
语法结构
示例:确保文件关闭
4️⃣简化资源管理:with 关键字
核心原
示例 1:简化文件操作
示例 2:其他资源管理场景
5️⃣主动触发异常:raise 语句
基本语法
示例 1:主动触发异常
示例 2:重新抛出异常
四、Python3 自定义异常:处理 “业务专属错误”
1️⃣自定义异常的规则
2️⃣示例 1:简单自定义异常
3️⃣示例 2:复杂场景(多自定义异常)
五、Python3 assert(断言):提前 “排查隐患”
1️⃣基本语法
2️⃣示例 1:环境检查
3️⃣示例 2:参数校验
4️⃣注意事项
六、总结:异常处理的 “最佳实践”
对于 Python 初学者来说,代码运行时遇到报错往往会手足无措。其实,错误和异常并非 “洪水猛兽”,而是程序在 “提醒” 我们哪里需要优化。掌握错误与异常的处理方法,是写出健壮、可靠 Python 代码的关键一步。
一、Python3 错误的分类:先搞懂 “错在哪”
在 Python3 中,程序运行出现的问题主要分为两类:语法错误和运行时异常。二者的本质区别在于:语法错误是 “代码写得不对”,程序根本无法启动;运行时异常是 “代码语法没问题,但执行中出了意外”,程序能启动但会中途报错。
1️⃣语法错误:最基础的 “拼写错误”
语法错误又称 “解析错误”,是初学者最常踩的坑 —— 本质是代码违反了 Python 的语法规则,导致解释器无法识别和执行。
常见场景与示例
比如少写冒号、括号不匹配、缩进错误等:
python
# 错误示例:while 循环后少写冒号,print 函数括号不完整
while True print("Hello world')
运行后会出现如下报错,解释器会贴心地用箭头指出错误位置:
plaintext
File "<stdin>", line 1while True print("Hello world')^
SyntaxError: invalid syntax # 语法错误:无效语法
关键特点
- 语法错误会直接阻断程序运行,解释器无法继续解析代码;
- 必须修改代码中的语法问题(如补全冒号、修正括号),程序才能正常启动;
- 常见的语法错误还包括
IndentationError
(缩进错误),比如 Tab 和空格混合使用导致的TabError
。
2️⃣运行时异常:“代码能跑,但中途出问题”
即使代码语法完全正确,执行过程中也可能因为 “逻辑漏洞” 或 “外部条件不满足” 触发错误,这就是运行时异常。与语法错误不同,异常是可以被程序 “捕获并处理” 的,避免程序直接崩溃。
常见场景与示例
最典型的例子是 “除数为零”—— 语法上没问题,但数学逻辑不允许:
python
# 代码语法正确,但执行时会触发异常
print(1 / 0)
运行后会出现 “异常追踪信息”,明确指出异常类型和位置:
plaintext
Traceback (most recent call last):File "d:\py3\hello.py", line 1, in <module>print(1 / 0)
ZeroDivisionError: division by zero # 异常类型:除数为零
关键特点
- 程序能正常启动,但执行到特定代码行时触发异常;
- 异常信息会显示 “异常类型”(如
ZeroDivisionError
)和 “错误位置”,便于定位问题; - 通过后续的 “异常处理机制”,可以捕获这些异常,让程序继续运行(比如提示 “除数不能为零”,而不是直接崩溃)。
二、Python3 常见异常类型:认识 “敌人” 才能应对
Python 内置了数十种异常类型,每种类型对应特定的错误场景。初学者无需死记硬背,但要熟悉最常用的几种,方便快速定位问题。以下是按 “使用频率” 排序的核心异常类型:
异常类型 | 触发场景说明 | 示例 |
---|---|---|
ZeroDivisionError | 除法运算中,除数为 0 | 1 / 0 |
TypeError | 操作或函数应用于 “不适当类型” 的对象 | 1 + "2" (整数和字符串无法相加) |
ValueError | 函数参数 “类型正确但值无效” | int("abc") (字符串是文本,无法转整数) |
IndexError | 访问序列(列表、字符串等)时,索引超出范围 | lst = [1,2]; lst[3] |
KeyError | 访问字典时,键(key)不存在 | dict = {"a":1}; dict["b"] |
AttributeError | 访问对象的属性或方法时,对象没有该属性 / 方法 | s = "hello"; s.append("!") (字符串无 append 方法) |
FileNotFoundError | 尝试打开不存在的文件(属于 OSError 的子类) | open("不存在的文件.txt") |
NameError | 使用未定义的变量或函数名 | print(undefined_var) |
AssertionError | 断言语句(assert )的条件为 False | assert 1 == 2 |
KeyboardInterrupt | 用户手动中断程序(如按 Ctrl+C ) | 运行程序时按 Ctrl+C |
补充说明
- 所有异常的 “祖宗” 是
BaseException
,但我们常用的异常(如上述所有类型)都继承自Exception
(BaseException
的子类); - 异常类型的命名有规律:大多以 “Error” 结尾,便于识别(如
ZeroDivisionError
、TypeError
)。
三、Python3 异常的处理:让程序 “优雅容错”
遇到异常时,默认行为是 “程序崩溃并打印异常信息”。但通过 try/except
等语句,我们可以 “捕获异常” 并执行自定义逻辑(如提示用户、记录日志),让程序继续运行 —— 这就是 “异常处理”。
1️⃣核心语法:try/except
基础用法
try/except
是异常处理的核心结构,逻辑是:“尝试执行一段代码,如果出异常就处理,没异常就正常执行”。
基本语法结构
python
try:# 尝试执行的代码(可能触发异常的代码)可能出错的代码块
except [异常类型1, 异常类型2, ...] as 异常变量:# 若触发指定类型的异常,执行这里的代码异常处理逻辑(如提示、日志)
-
示例 1:捕获特定异常(推荐)
明确捕获 “除数为零” 异常,避免程序崩溃:
python
try:result = 1 / 0 # 可能触发 ZeroDivisionError
except ZeroDivisionError as e:# 打印自定义提示,同时显示异常信息print(f"出错了!原因:{e}") # 输出:出错了!原因:division by zero
-
示例 2:捕获多个异常
如果一段代码可能触发多种异常,可以用元组指定多个异常类型:
python
try:# 可能触发 TypeError(如 1+"2")或 ZeroDivisionError(如 1/0)num = int(input("请输入一个数字:"))result = 10 / num
except (TypeError, ZeroDivisionError) as e:print(f"输入无效或除数为零!原因:{e}")
示例 3:通用异常捕获(谨慎使用)
如果不确定会触发什么异常,可以用 Exception
(所有常规异常的基类)捕获所有异常。但不建议滥用 —— 会掩盖未知错误,不利于调试:
python
try:num = int(input("请输入一个数字:"))result = 10 / num
except Exception as e:print(f"发生未知错误!原因:{e}")
2️⃣进阶用法:try/except/else
else
子句是 “可选的”,作用是:当 try
中的代码 “没有触发任何异常” 时,执行 else
中的逻辑。
语法结构
python
try:可能出错的代码块
except 异常类型 as e:异常处理逻辑
else:# 只有 try 中无异常时,才执行这里无异常时的逻辑
示例:文件读取场景
判断文件是否能正常打开,若打开成功则读取内容(避免将 “读取文件” 的逻辑放入 try
中,减少不必要的异常捕获):
python
import sys# 从命令行参数获取文件名
for filename in sys.argv[1:]:try:# 尝试打开文件(可能触发 FileNotFoundError)f = open(filename, 'r')except FileNotFoundError as e:print(f"无法打开文件:{filename},原因:{e}")else:# 打开成功,读取文件行数并打印lines = f.readlines()print(f"{filename} 有 {len(lines)} 行内容")f.close() # 记得关闭文件
3️⃣必学用法:try/except/finally
finally
子句是 “可选的”,但极其重要 ——无论 try
中是否触发异常,finally
中的代码 “一定会执行”。常用于 “释放资源”(如关闭文件、断开数据库连接)。
语法结构
python
try:可能出错的代码块
except 异常类型 as e:异常处理逻辑
else:无异常时的逻辑
finally:# 无论是否有异常,都会执行这里资源释放逻辑(如关闭文件、断开连接)
-
示例:确保文件关闭
即使读取文件时触发异常,finally
也会执行 f.close()
,避免文件资源泄漏:
python
try:f = open("test.txt", 'r')content = f.read()print("文件内容:", content)
except FileNotFoundError as e:print(f"出错了:{e}")
finally:# 无论是否有异常,都关闭文件if 'f' in locals(): # 判断 f 是否已定义(避免未打开文件时报错)f.close()print("文件已关闭")
4️⃣简化资源管理:with
关键字
try/finally
虽然能确保资源释放,但代码略显繁琐。Python 提供了 with
关键字,它封装了 try/finally
的逻辑,能 “自动管理资源”(如自动关闭文件、自动断开连接),让代码更简洁。
-
核心原理
with
语句依赖 “上下文管理器”(实现 __enter__
和 __exit__
方法的类):
- 进入
with
块时,调用__enter__
方法(如打开文件); - 离开
with
块时(无论是否有异常),自动调用__exit__
方法(如关闭文件)。
-
示例 1:简化文件操作
用 with
打开文件,无需手动调用 close()
—— 离开 with
块时会自动关闭:
python
# 自动打开文件,离开 with 块时自动关闭
with open("test.txt", 'w') as f:f.write("Hello, Python!") # 写入内容# 此时文件已自动关闭,无需手动 f.close()
-
示例 2:其他资源管理场景
with
不仅用于文件,还可用于数据库连接、线程锁等场景。例如用 with
管理数据库连接(需依赖具体数据库库):
python
import sqlite3# 自动连接数据库,离开 with 块时自动关闭连接
with sqlite3.connect('test.db') as conn:cursor = conn.cursor()cursor.execute("CREATE TABLE IF NOT EXISTS users (id INT, name TEXT)")conn.commit()
5️⃣主动触发异常:raise
语句
除了 “被动捕获异常”,我们还可以用 raise
语句 “主动触发异常”—— 常用于 “自定义业务规则校验”(如判断参数是否符合要求)。
基本语法
python
# 1. 触发指定类型的异常
raise 异常类型("异常描述信息")# 2. 重新抛出捕获的异常(不处理,交给上层处理)
try:代码块
except 异常类型 as e:# 打印日志后,重新抛出异常print(f"捕获到异常:{e}")raise # 重新抛出当前异常
-
示例 1:主动触发异常
判断参数 x
是否大于 5,若大于则触发 Exception
并提示:
python
x = 10
if x > 5:# 主动触发异常,描述信息会显示在报错中raise Exception(f"x 不能大于 5,当前 x 的值为:{x}")
运行后报错:
plaintext
Traceback (most recent call last):File "d:\py3\test.py", line 3, in <module>raise Exception(f"x 不能大于 5,当前 x 的值为:{x}")
Exception: x 不能大于 5,当前 x 的值为:10
-
示例 2:重新抛出异常
捕获异常后不处理,而是交给上层代码处理(比如在日志记录后,让调用者处理异常):
python
def calculate(x):try:if x == 0:raise ZeroDivisionError("x 不能为 0")return 10 / xexcept ZeroDivisionError as e:# 记录日志后,重新抛出异常print(f"日志:捕获到异常:{e}")raise # 重新抛出,让调用者处理# 调用函数,处理重新抛出的异常
try:calculate(0)
except ZeroDivisionError as e:print(f"调用者处理异常:{e}")
运行结果:
plaintext
日志:捕获到异常:x 不能为 0
调用者处理异常:x 不能为 0
四、Python3 自定义异常:处理 “业务专属错误”
Python 内置的异常类型能覆盖大多数通用场景,但在实际开发中,我们可能需要 “自定义异常” 来描述 “业务专属的错误”(如 “用户余额不足”“订单状态无效”)。
1️⃣自定义异常的规则
- 自定义异常类必须继承自
Exception
(或其子类),不能直接继承BaseException
(避免捕获系统级异常,如KeyboardInterrupt
); - 异常类的命名建议以 “Error” 结尾,符合 Python 命名规范;
- 可以重写
__init__
方法(初始化异常信息)和__str__
方法(自定义异常的字符串描述)。
2️⃣示例 1:简单自定义异常
定义一个 “余额不足异常”,用于处理用户支付时的余额问题:
python
# 自定义异常类,继承自 Exception
class InsufficientBalanceError(Exception):# 重写 __init__ 方法,接收余额和所需金额def __init__(self, current_balance, required_amount):self.current_balance = current_balanceself.required_amount = required_amount# 重写 __str__ 方法,自定义异常描述def __str__(self):return f"余额不足!当前余额:{self.current_balance},所需金额:{self.required_amount}"# 模拟支付函数
def pay(amount):current_balance = 100 # 假设用户当前余额为 100if amount > current_balance:# 主动触发自定义异常raise InsufficientBalanceError(current_balance, amount)print(f"支付成功!支付金额:{amount},剩余余额:{current_balance - amount}")# 测试:捕获自定义异常
try:pay(150) # 所需金额 150 > 余额 100,触发异常
except InsufficientBalanceError as e:print(f"支付失败:{e}") # 输出:支付失败:余额不足!当前余额:100,所需金额:150
3️⃣示例 2:复杂场景(多自定义异常)
当一个模块需要多种异常时,建议先定义一个 “基础异常类”,再基于它定义多个 “子类异常”—— 便于统一管理和捕获。
例如,定义一个 “订单相关的基础异常”,再衍生 “订单不存在”“订单已取消” 两种异常:
python
# 订单相关的基础异常类
class OrderError(Exception):pass# 子类1:订单不存在异常
class OrderNotFoundError(OrderError):def __str__(self):return f"订单 {self.order_id} 不存在"def __init__(self, order_id):self.order_id = order_id# 子类2:订单已取消异常
class OrderCancelledError(OrderError):def __str__(self):return f"订单 {self.order_id} 已取消,无法操作"def __init__(self, order_id):self.order_id = order_id# 模拟获取订单状态的函数
def get_order_status(order_id):# 模拟订单数据:key 是订单ID,value 是状态("exists"/"cancelled"/"not_found")orders = {"1001": "exists", "1002": "cancelled"}if order_id not in orders:raise OrderNotFoundError(order_id)if orders[order_id] == "cancelled":raise OrderCancelledError(order_id)return f"订单 {order_id} 状态正常"# 测试:捕获多个自定义异常
try:print(get_order_status("1003")) # 订单不存在
except OrderNotFoundError as e:print(f"错误:{e}") # 输出:错误:订单 1003 不存在
except OrderCancelledError as e:print(f"错误:{e}")
五、Python3 assert(断言):提前 “排查隐患”
assert
(断言)是 Python 提供的一种 “调试工具”,用于在代码中 “提前判断条件是否满足”—— 如果条件为 False
,则触发 AssertionError
异常,直接终止程序;如果条件为 True
,则不影响程序运行。
1️⃣基本语法
python
# 语法1:仅判断条件,无自定义提示
assert 条件表达式# 语法2:条件为 False 时,显示自定义提示
assert 条件表达式, "条件不满足时的提示信息"
等价于以下逻辑(但 assert
更简洁):
python
if not 条件表达式:raise AssertionError("条件不满足时的提示信息")
2️⃣示例 1:环境检查
确保代码只在 Linux 系统下运行,否则提前报错(避免程序运行后出现兼容性问题):
python
import sys# 检查当前系统是否为 Linux(sys.platform 返回系统标识,如 "linux"、"win32")
assert "linux" in sys.platform, "该代码只能在 Linux 系统下执行!"# 如果条件满足,才执行后续代码
print("当前系统是 Linux,开始执行程序...")
若在 Windows 系统运行,会报错:
plaintext
AssertionError: 该代码只能在 Linux 系统下执行!
3️⃣示例 2:参数校验
在函数中用 assert
校验参数是否符合要求(如参数必须为正数):
python
运行
def calculate_square_root(num):# 断言:num 必须大于等于 0(否则无法计算平方根)assert num >= 0, f"参数必须为非负数,当前参数:{num}"return num ** 0.5# 测试
print(calculate_square_root(4)) # 条件满足,输出 2.0
print(calculate_square_root(-4)) # 条件不满足,触发 AssertionError
运行后报错:
plaintext
AssertionError: 参数必须为非负数,当前参数:-4
4️⃣注意事项
assert
主要用于 “调试阶段”,不能替代if
判断或异常处理 —— 生产环境中,Python 解释器可能通过-O
选项(优化模式)禁用assert
,导致断言失效;- 不要用
assert
处理 “预期内的错误”(如用户输入无效),应用if
判断或raise
异常; assert
的核心作用是 “提前暴露程序中的逻辑漏洞”(如 “这里的参数不可能为负”),便于调试。
六、总结:异常处理的 “最佳实践”
- 优先捕获特定异常:避免用
Exception
捕获所有异常,防止掩盖未知错误; - 用
finally
或with
管理资源:确保文件、数据库连接等资源被正确释放,避免泄漏; - 自定义异常要规范:继承
Exception
,命名以 “Error” 结尾,复杂场景用 “基础类 + 子类”; assert
仅用于调试:不依赖assert
处理业务逻辑,生产环境禁用assert
;- 异常信息要清晰:在
raise
或except
中添加详细的描述信息(如参数值、文件名),便于定位问题。