在 Python 中,异常(Exception) 是程序运行时发生的非预期错误(如除数为 0、访问不存在的列表索引等),会中断程序的正常执行。合理处理异常可以避免程序崩溃,提供友好的错误提示,并确保资源正确释放(如关闭文件、数据库连接)。本文将从异常的基本概念、处理机制、内置异常、自定义异常到最佳实践,全面讲解 Python 异常。
一、异常的基本概念
1. 什么是异常?
异常是程序运行时的 “意外事件”,例如:
- 试图除以 0(
ZeroDivisionError); - 访问列表中不存在的索引(
IndexError); - 打开不存在的文件(
FileNotFoundError); - 类型不匹配(如用字符串加数字,
TypeError)。
如果不处理异常,程序会直接崩溃并抛出错误信息(Traceback):
# 示例:未处理的异常导致程序崩溃
print(10 / 0) # 运行时触发 ZeroDivisionError
# 输出:
# Traceback (most recent call last):
# File "test.py", line 1, in <module>
# print(10 / 0)
# ZeroDivisionError: division by zero
2. 异常与语法错误的区别
-
语法错误(SyntaxError):代码不符合 Python 语法规则(如缺少冒号、括号不匹配),程序在运行前就会报错,无法执行。
if True # 缺少冒号,语法错误 print("hello") # 输出:SyntaxError: expected ':' -
异常(Exception):代码语法正确,但运行时出现错误(如上述例子),程序可以通过异常处理机制捕获并处理。
二、异常处理的基本机制
Python 用 try-except 语句捕获并处理异常,核心逻辑是:“尝试执行可能出错的代码,若出错则执行预设的处理逻辑”。
1. 基础结构:try-except
try:
# 可能触发异常的代码块
risky_code()
except 异常类型1:
# 若触发异常类型1,执行此代码块
handle_error1()
except 异常类型2:
# 若触发异常类型2,执行此代码块
handle_error2()
示例:处理除以 0 的异常
try:
num = int(input("请输入一个除数:"))
result = 10 / num
print(f"10 / {num} = {result}")
except ZeroDivisionError:
# 捕获“除数为0”的异常
print("错误:除数不能为0!")
except ValueError:
# 捕获“输入无法转为整数”的异常(如输入字母)
print("错误:请输入有效的整数!")
- 当
try块中代码触发ZeroDivisionError时,执行第一个except块; - 若触发
ValueError(如输入 “abc”),执行第二个except块; - 若未触发任何异常,
except块不执行,程序继续运行。
2. 捕获所有异常:except Exception 或 except
若想捕获所有非系统退出类异常(不建议滥用),可使用 except Exception(Exception 是所有内置非系统异常的基类):
try:
# 可能出错的代码
10 / 0
except Exception as e: # 用 as e 捕获异常对象,可获取错误信息
print(f"发生错误:{e}") # 输出:发生错误:division by zero
更简单(但更不推荐)的写法是 except:(捕获所有异常,包括系统退出异常如 KeyboardInterrupt):
try:
10 / 0
except: # 不推荐:过度捕获,可能隐藏bug
print("发生了未知错误")
3. else 子句:无异常时执行
else 子句可选,用于定义当 try 块无异常时执行的代码(与 except 互斥):
try:
num = int(input("请输入一个正数:"))
if num <= 0:
raise ValueError("必须输入正数") # 主动抛异常(见后文)
except ValueError as e:
print(f"输入错误:{e}")
else:
# 无异常时执行(num 是正数)
print(f"你输入的正数是:{num}")
4. finally 子句:无论是否有异常都执行
finally 子句可选,用于定义无论 try 块是否触发异常,都必须执行的代码(通常用于释放资源,如关闭文件、网络连接):
file = None
try:
file = open("test.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("错误:文件不存在")
finally:
# 无论是否出错,都关闭文件(释放资源)
if file:
file.close()
print("文件已关闭")
执行顺序:
- 若
try无异常:try→else→finally - 若
try有异常:try→except→finally
三、Python 内置异常类型
Python 定义了大量内置异常,覆盖各种常见错误场景。以下是最常用的几类:
1. 基础异常类
BaseException:所有异常的基类(包括系统退出异常如SystemExit)。Exception:所有非系统退出异常的基类(日常处理的异常都继承自它)。
2. 常见具体异常
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
TypeError |
操作或函数应用于不适当类型的对象 | 1 + "2"(int 加 str) |
ValueError |
操作或函数接收到的参数类型正确但值无效 | int("abc")(字符串无法转为整数) |
ZeroDivisionError |
除数为 0 | 10 / 0 |
IndexError |
访问序列(列表、元组等)中不存在的索引 | [1,2][3](列表只有 3 个元素,索引 0-2) |
KeyError |
访问字典中不存在的键 | {"name": "Alice"}["age"] |
FileNotFoundError |
尝试打开不存在的文件(IOError 的子类) |
open("nonexist.txt") |
AttributeError |
访问对象不存在的属性 | "hello".nonexist_attr |
NameError |
使用未定义的变量 | print(undefined_var) |
IndentationError |
缩进错误(语法错误的一种) | if True: print("hello")(缺少缩进) |
示例:触发 KeyError 并处理
user = {"name": "Bob", "age": 20}
try:
print(user["gender"]) # 访问不存在的键 "gender"
except KeyError as e:
print(f"错误:键 {e} 不存在") # 输出:错误:键 'gender' 不存在
3. 异常的继承关系
部分异常存在继承关系,例如:ArithmeticError(算术错误)是 ZeroDivisionError、OverflowError 等的父类;LookupError(查找错误)是 IndexError、KeyError 等的父类。
捕获父类异常会同时捕获其子类异常:
try:
[1,2][3] # 触发 IndexError(LookupError 的子类)
except LookupError:
print("捕获到查找错误(IndexError 或 KeyError)") # 会执行
四、主动抛出异常:raise 语句
除了被动捕获运行时异常,还可以用 raise 语句主动抛出异常,用于在满足特定条件时中断程序并提示错误(如参数校验)。
1. 基本用法:raise 异常类型 或 raise 异常对象
def divide(a, b):
if b == 0:
# 主动抛出 ZeroDivisionError
raise ZeroDivisionError("除数不能为0!") # 可自定义错误信息
return a / b
try:
divide(10, 0)
except ZeroDivisionError as e:
print(f"捕获到错误:{e}") # 输出:捕获到错误:除数不能为0!
2. 重新抛出异常:raise
在 except 块中可使用 raise 不带参数,将捕获的异常重新抛出(用于多层异常处理):
try:
10 / 0
except ZeroDivisionError as e:
print(f"内层处理:{e},准备向上抛出")
raise # 重新抛出异常,让外层处理
运行结果:
内层处理:division by zero,准备向上抛出
Traceback (most recent call last):
File "test.py", line 2, in <module>
10 / 0
ZeroDivisionError: division by zero
3. 异常链:raise ... from
用 raise 新异常 from 原异常 可将两个异常关联(原异常是新异常的原因),便于追踪错误根源:
try:
int("abc") # 触发 ValueError
except ValueError as original_e:
# 抛出新异常,并关联原异常
raise TypeError("转换失败,输入不是数字") from original_e
运行结果(会显示异常链):
Traceback (most recent call last):
File "test.py", line 2, in <module>
int("abc")
ValueError: invalid literal for int() with base 10: 'abc'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test.py", line 5, in <module>
raise TypeError("转换失败,输入不是数字") from original_e
TypeError: 转换失败,输入不是数字
五、自定义异常
当内置异常无法满足业务需求时(如 “用户年龄必须大于 18 岁”),可通过继承 Exception 类定义自定义异常。
1. 定义自定义异常
# 自定义异常:继承自 Exception
class AgeError(Exception):
"""自定义异常:用于处理年龄不合法的场景"""
def __init__(self, message):
self.message = message # 存储错误信息
# 可选:自定义异常的字符串表示
def __str__(self):
return f"AgeError: {self.message}"
2. 使用自定义异常
def check_age(age):
if age < 18:
# 抛出自定义异常
raise AgeError("年龄必须大于等于18岁")
print(f"年龄合法:{age}岁")
try:
check_age(15)
except AgeError as e:
print(f"捕获到自定义异常:{e}") # 输出:捕获到自定义异常:AgeError: 年龄必须大于等于18岁
自定义异常通常用于大型项目,可更精确地区分错误类型(如业务错误、参数错误、权限错误等)。
六、异常处理的最佳实践
合理的异常处理能提高程序的健壮性和可维护性,以下是关键原则:
1. 只捕获特定异常,避免过度捕获
不要用 except: 或 except Exception: 捕获所有异常,这会隐藏真正的错误(如拼写错误、逻辑错误)。应精确指定需要处理的异常:
# 推荐:只捕获需要处理的异常
try:
file = open("data.txt")
except FileNotFoundError:
print("文件不存在,使用默认数据")
data = "default"
# 不推荐:过度捕获,可能隐藏其他错误(如变量名拼写错误)
try:
file = open("data.txt")
except: # 会捕获所有异常,包括 NameError 等
print("发生错误")
2. 提供具体的错误信息
异常处理时,应输出清晰的错误信息(如错误原因、影响范围),便于调试:
try:
user = {"name": "Alice"}
print(user["age"])
except KeyError as e:
# 具体说明错误:哪个键不存在,在处理什么数据
print(f"处理用户数据时出错:键 {e} 不存在,用户信息为 {user}")
3. 用 finally 释放资源
涉及文件、网络连接、数据库连接等资源时,必须在 finally 中释放,确保资源不泄露:
# 示例:用 finally 关闭数据库连接
import sqlite3
conn = None
try:
conn = sqlite3.connect("mydb.db")
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
except sqlite3.Error as e:
print(f"数据库错误:{e}")
finally:
if conn:
conn.close() # 无论是否出错,都关闭连接
print("数据库连接已关闭")
4. 避免在异常处理中忽略错误
不要捕获异常后不做任何处理(“吞掉异常”),这会导致错误无法被发现:
# 不推荐:忽略异常,隐藏问题
try:
10 / 0
except ZeroDivisionError:
pass # 什么都不做,程序看似正常,实则出错
5. 自定义异常用于业务逻辑
在大型项目中,用自定义异常区分业务错误(如 PermissionDeniedError、ResourceNotFoundError),使代码更清晰:
class PermissionDeniedError(Exception):
"""无权限访问时抛出"""
def access_resource(user, resource):
if user.role != "admin":
raise PermissionDeniedError(f"用户 {user.name} 无权限访问 {resource}")
# 正常访问逻辑...
总结
异常是 Python 处理运行时错误的核心机制,通过 try-except-else-finally 结构可实现:
- 捕获并处理特定异常,避免程序崩溃;
- 用
else执行无异常时的逻辑; - 用
finally确保资源释放; - 用
raise主动抛出异常,或定义自定义异常处理业务错误。
遵循 “捕获特定异常、提供清晰信息、释放资源” 等最佳实践,能编写更健壮、易维护的代码