异常处理
# Python 异常处理
在任何编程语言中,错误都是不可避免的。一个健壮的程序不仅要能正确执行任务,还应该能够优雅地处理各种预料之外的错误。在 Python 中,这种错误处理机制就是异常处理。
# 1. 什么是异常?
异常 (Exception) 是程序在执行期间发生的错误事件。当 Python 解释器遇到一个它无法处理的错误情况时(例如,除以零、访问不存在的列表索引),它会创建一个异常对象并“抛出”它。
如果这个异常没有被程序的任何部分“捕获”并处理,程序就会立即终止,并显示一个称为“回溯 (Traceback)”的错误消息。
# 示例1:除以零
# result = 10 / 0
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# ZeroDivisionError: division by zero
# 示例2:类型错误
# result = '2' + 3
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: can only concatenate str (not "int") to str
2
3
4
5
6
7
8
9
10
11
异常处理的核心目标就是:在异常发生时,捕获它,执行一些补救或记录措施,然后让程序继续执行(如果可能),而不是直接崩溃。
# 2. try...except
:捕获异常
try...except
语句块是 Python 异常处理的基础。
try
块: 将你预感可能会出错的代码放在这里。except
块: 如果try
块中的代码真的发生了异常,解释器会立即跳转到except
块来执行这里的代码。
基本语法:
try:
# 可能会引发异常的代码
code_that_might_fail()
except ExceptionType:
# 如果发生了指定类型的异常,执行这里的代码
handle_the_error()
2
3
4
5
6
示例:处理用户输入
try:
num_str = input("请输入一个数字: ")
num = int(num_str)
print(f"你输入的数字是: {num}")
except ValueError:
# 如果用户输入的不是有效的数字字符串,int() 会引发 ValueError
print("错误:请输入一个有效的整数!")
print("程序继续执行...")
# 如果没有 try...except,当用户输入 "abc" 时,程序会崩溃。
# 有了它,程序会打印错误信息,然后继续执行到这里。
2
3
4
5
6
7
8
9
10
11
# 2.1 捕获特定异常
捕获过于宽泛的异常(如 Exception
)或使用裸露的 except:
是一种不好的做法,因为它会掩盖所有类型的错误,让你难以调试。最佳实践是尽可能精确地捕获你预期的异常。
try:
numerator = int(input("输入分子: "))
denominator = int(input("输入分母: "))
result = numerator / denominator
print(f"结果是: {result}")
except ValueError:
print("错误:分子和分母都必须是整数。")
except ZeroDivisionError:
print("错误:分母不能为零!")
2
3
4
5
6
7
8
9
# 2.2 一次捕获多种异常
你可以将多个异常类型放在一个元组里,用一个 except
块来处理。
try:
# ... 一些代码 ...
pass
except (TypeError, ValueError, IndexError):
print("发生了类型、值或索引错误。")
2
3
4
5
# 2.3 获取异常对象
有时,你需要获取异常对象的具体信息(例如,错误消息)。可以使用 as
关键字。
try:
with open("non_existent_file.txt", "r") as f:
content = f.read()
except FileNotFoundError as e:
print("文件未找到!")
print(f"详细错误信息: {e}")
print(f"异常类型: {type(e)}")
# 输出:
# 文件未找到!
# 详细错误信息: [Errno 2] No such file or directory: 'non_existent_file.txt'
# 异常类型: <class 'FileNotFoundError'>
2
3
4
5
6
7
8
9
10
11
12
# 3. try...except...else
结构
else
子句提供了一种方式,用于指定当 try
块中没有发生任何异常时才需要执行的代码。
为什么要用 else
?
它可以帮助你将可能引发异常的代码(放在 try
中)与仅在成功后才应执行的代码(放在 else
中)清晰地分开,提高代码可读性。
try:
numerator = int(input("输入分子: "))
denominator = int(input("输入分母: "))
result = numerator / denominator
except ValueError:
print("请输入有效的整数。")
except ZeroDivisionError:
print("分母不能为零!")
else:
# 只有在 try 块中所有语句都成功执行后,这里才会执行
print("计算成功!")
print(f"结果是: {result}")
2
3
4
5
6
7
8
9
10
11
12
# 4. try...finally
:确保执行清理
finally
子句中的代码无论是否发生异常,都保证会被执行。这使得它成为执行“清理”操作(如关闭文件、释放网络连接、解锁资源)的理想场所。
f = None # 在 try 外面初始化
try:
f = open("my_file.txt", "w")
f.write("你好,Python!")
# 假设这里发生了一个错误
# 10 / 0
except Exception as e:
print(f"发生了一个错误: {e}")
finally:
# 无论上面是否出错,这里都会执行
if f:
print("正在关闭文件...")
f.close()
2
3
4
5
6
7
8
9
10
11
12
13
注意: 更 Pythonic 的方式是使用 with
语句来管理资源,它会自动处理文件的打开和关闭,即使发生异常也是如此,这在内部其实就是利用了 try...finally
。
# 5. raise
:主动抛出异常
你可以使用 raise
关键字在代码中主动地、显式地引发一个异常。
为什么要主动抛出异常? 当你的函数接收到不合法的参数,或者程序进入了一个不正常的状态时,你可以通过抛出异常来立即终止当前操作,并通知调用者“出错了”。
def set_age(age):
if not isinstance(age, int) or age < 0 or age > 120:
# 如果年龄不合法,就抛出 ValueError 异常
raise ValueError("年龄必须是 0 到 120 之间的整数!")
print(f"年龄已设置为: {age}")
try:
set_age(25) # 正常
set_age(-5) # 引发异常
except ValueError as e:
print(f"设置年龄失败: {e}")
# 输出:
# 年龄已设置为: 25
# 设置年龄失败: 年龄必须是 0 到 120 之间的整数!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6. 自定义异常
虽然 Python 的内置异常已经很丰富,但有时你需要创建自己的异常类型来表示应用程序特有的错误情况。
自定义异常通过继承内置的 Exception
类(或其子类)来创建。
# 1. 创建自定义异常类
class NetworkError(Exception):
"""表示网络相关的错误"""
def __init__(self, message, error_code):
super().__init__(message) # 调用父类的构造函数
self.error_code = error_code
# 2. 在代码中使用它
def fetch_data_from_server():
# 模拟一个网络错误
is_connected = False
if not is_connected:
raise NetworkError("无法连接到服务器", 503)
# 3. 捕获自定义异常
try:
fetch_data_from_server()
except NetworkError as e:
print(f"捕获到网络错误: {e}")
print(f"错误码: {e.error_code}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
自定义异常的好处:
- 更具描述性:
NetworkError
比通用的Exception
能更清晰地表达错误类型。 - 更好的组织: 调用者可以专门捕获你的
NetworkError
,并与其他的ValueError
或TypeError
区分开来。 - 可扩展性: 你可以在异常中添加额外的属性(如
error_code
),携带更多关于错误的上下文信息。
# 7. 常见内置异常
了解 Python 的常用内置异常对于编写健壮的代码至关重要。以下是一些最常见的异常及其触发条件:
ValueError
: 传入的参数类型正确,但值不合适。# int() 无法将 "abc" 转换为整数 int("abc")
1
2TypeError
: 对不同类型的对象执行了不支持的操作。# 字符串不能和数字相加 "2" + 3
1
2IndexError
: 试图访问序列(如列表、元组)中不存在的索引。my_list = [1, 2, 3] print(my_list[3]) # 索引只到 2
1
2KeyError
: 试图访问字典中不存在的键。my_dict = {"name": "Alice"} print(my_dict["age"]) # "age" 键不存在
1
2FileNotFoundError
: 试图打开一个不存在的文件。with open("ghost.txt", "r") as f: pass
1
2ZeroDivisionError
: 尝试将一个数除以零。result = 10 / 0
1AttributeError
: 试图访问或调用一个对象不存在的属性或方法。my_string = "hello" my_string.append("!") # 字符串没有 .append() 方法,那是列表的
1
2NameError
: 使用了一个未被定义的变量。print(undefined_variable)
1
# 8. 异常处理最佳实践
- 精确捕获:尽量捕获最具体的异常,避免使用裸露的
except:
。 - 不要压制异常:避免空的
except
块(except: pass
),因为它会悄无声息地吞掉错误,让调试变得异常困难。如果你确实需要忽略某个异常,最好加上注释说明原因。 - 保持
try
块短小:只在try
块中放入你真正认为可能出错的代码。 - 利用
finally
进行清理:使用finally
来确保重要的清理代码(如关闭文件)总能执行,或者优先使用with
语句。 - 创建自定义异常:为你的库或应用创建有意义的自定义异常,使错误处理更有条理。
- 记录异常:在捕获异常后,使用
logging
模块记录详细的错误信息,这对于生产环境中的问题排查至关重要。