- Published on
3.13.异常
- Authors

- Name
- xiaobai
1.概述
异常是程序运行时发生的错误事件,会中断正常的指令流。Python 提供了强大的异常处理机制,让程序能够优雅地处理错误情况,提高程序的健壮性和用户体验。
2.核心价值
- 提高程序的健壮性:避免程序因错误而崩溃
- 提供友好的错误信息:给用户清晰的错误提示
- 确保资源正确释放:防止资源泄漏
- 便于调试和维护:提供详细的错误信息
3.基本语法
3.1.try-except 结构
try:
# 可能发生异常的代码块
...
except 异常类型 as 变量:
# 异常发生时的处理代码
...
3.2.工作机制
- Python 解释器依次运行
try块中的代码 - 一旦遇到错误(异常),立即跳转到匹配的
except块处理 - 如果没有发生异常,则
except块会跳过,不会执行 - 如果
try块中的异常类型与except指定的不符,异常将继续向上传递
3.3.基本示例
try:
x = int(input("请输入一个整数: "))
y = 10 / x
print("结果:", y)
except ValueError:
print("输入的不是有效整数!")
except ZeroDivisionError:
print("不能除以零!")
4.内置异常类型
4.1.异常层次结构
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ ├── FloatingPointError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ ├── PermissionError
│ └── TimeoutError
├── ValueError
├── TypeError
├── AttributeError
├── NameError
└── RuntimeError
4.2.常见异常类型
| 异常类型 | 描述 | 示例 |
|---|---|---|
ValueError | 传入无效参数 | int('abc') |
TypeError | 操作或函数应用的对象类型不正确 | '2' + 2 |
IndexError | 序列索引超出范围 | [1, 2, 3][10] |
KeyError | 字典中查找不存在的键 | {'a': 1}['b'] |
AttributeError | 对象不存在属性或方法 | 'hello'.nonexistent_method() |
NameError | 未声明/未定义变量被访问 | print(undefined_variable) |
FileNotFoundError | 尝试打开不存在的文件 | open('nonexistent_file.txt') |
ZeroDivisionError | 除法或模运算中除数为零 | 10 / 0 |
4.3.异常演示
exceptions = [
("10 / 0", ZeroDivisionError),
("int('abc')", ValueError),
("'2' + 2", TypeError),
("[1, 2, 3][10]", IndexError),
("{'a': 1}['b']", KeyError),
("'hello'.nonexistent_method()", AttributeError),
("print(undefined_variable)", NameError),
("open('nonexistent_file.txt')", FileNotFoundError)
]
for code, expected_exception in exceptions:
try:
exec(code)
except expected_exception as e:
print(f"{code:30} -> {type(e).__name__}: {e}")
5.抛出异常
5.1.使用 raise 语句
抛出异常指的是主动触发一个错误,让程序中断当前流程,并转交给异常处理机制。
5.1.1.基本语法
def divide(a, b):
if b == 0:
raise ZeroDivisionError("除数不能为零!")
return a / b
try:
divide(10, 0)
except ZeroDivisionError as e:
print("发生异常:", e)
5.1.2.raise 的用法
raise 异常类():抛出异常类的实例raise 异常类("错误信息"):抛出带错误信息的异常实例raise:在 except 块中重新抛出当前异常
5.1.3.常见场景
- 数据校验:参数不合法时,主动抛出异常阻止非法操作
- 接口规范:在自定义函数/类中,明确遇到非法状态或不支持的操作时抛出异常
5.2.重新抛出异常
重新抛出(re-raise)异常通常用于在捕获异常后执行一些操作(如日志、清理等),然后将异常继续向上传递。
def process_data(data):
try:
number = int(data)
result = 100 / number
return result
except ValueError:
print("数据格式错误,记录日志...")
raise # 重新抛出原始异常
except ZeroDivisionError:
print("除零错误,记录日志...")
raise # 重新抛出原始异常
try:
process_data("abc")
except Exception as e:
print(f"外层捕获: {e}")
5.3.异常链
异常链(Exception Chaining)是指在处理一个异常时,又引发了新的异常,可以用 raise ... from ... 的语法将新的异常和原始异常串联起来。
5.3.1.语法
try:
# 语句块,可能发生异常
except 原始异常 as e:
# 记录日志、清理等
raise 新异常("描述") from e # 指定异常链
5.3.2.示例
def process_file(filename):
try:
with open(filename) as f:
return f.read()
except FileNotFoundError as e:
# 封装为业务相关异常,链接原始异常
raise RuntimeError("文件处理失败") from e
try:
data = process_file("not_exist.txt")
except RuntimeError as err:
print("捕获到业务异常:", err)
print("原始异常为:", err.__cause__)
5.3.3.好处
- 更好地定位和排查复杂业务异常的根源
- 让异常日志和调用栈更加清晰,方便运维和开发调试
6.自定义异常
6.1.创建自定义异常类
自定义异常是指根据业务需要,从现有的异常基类(如Exception)派生出更加"特定语义"的异常类,用来表达程序中的特殊错误情况。
6.1.1.好处
- 语义明确:异常名称直接表达了业务意图,便于定位和维护
- 增强可读性:通过捕获自定义异常,代码逻辑清晰,易于管理不同错误
- 便于分层处理:可以设计继承结构,精准处理某一类或所有子类异常
6.1.2.基本写法
class MyCustomError(Exception):
"""自定义异常:用于描述特定业务错误"""
pass
6.1.3.带参数的异常
class DataFormatError(Exception):
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"{field} 格式错误: {message}")
# 使用示例
try:
raise DataFormatError("email", "缺少@符号")
except DataFormatError as e:
print(f"字段: {e.field}, 错误: {e.message}")
6.1.4.捕获自定义异常
try:
# 某些操作,可能抛出自定义异常
raise MyCustomError("Something went wrong!")
except MyCustomError as e:
print("捕获到自定义异常:", e)
7.上下文管理器与异常
7.1.with 语句的异常处理
with 语句(上下文管理器)可以优雅地管理资源的获取与释放,并自动处理异常。
7.1.1.工作机制
__enter__():进入with块时自动调用,用于获取资源with块内部:执行用户代码,如果发生异常会被自动捕获__exit__(exc_type, exc_val, exc_tb):离开with块时自动调用,无论是否有异常发生
7.1.2.示例
class FileOpener:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
print("文件已打开")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
print("文件已关闭")
if exc_type: # 发生了异常
print(f"发生异常:{exc_type.__name__}: {exc_val}")
return False # 返回False,异常不会被吞掉
try:
with FileOpener("demo.txt", "w") as f:
f.write("hello world\n")
raise ValueError("人为制造错误测试异常处理")
f.write("这句话不会执行")
except Exception as e:
print("外部捕获到异常:", e)
7.1.3.输出效果
文件已打开
文件已关闭
发生异常:ValueError: 人为制造错误测试异常处理
外部捕获到异常: 人为制造错误测试异常处理
7.1.4.小结
with语句中的上下文管理器,无论是否出错都能确保资源被正确释放- 可以在
__exit__方法中对异常进行统一处理和日志记录 - 记得仅在异常能安全处理时返回
True,否则让异常继续抛出 - 大量应用场景:文件操作、数据库连接、线程锁、网络通信等
8.最佳实践
8.1.1. 具体的异常捕获
# 不推荐 - 过于宽泛
try:
# 一些操作
pass
except:
pass
# 推荐 - 具体异常
try:
with open("data.txt", "r") as f:
data = f.read()
except FileNotFoundError:
print("文件不存在")
except PermissionError:
print("没有文件权限")
except OSError as e:
print(f"系统错误: {e}")
8.2.2. 避免空的 except 块
# 不推荐
try:
risky_operation()
except:
pass # 静默忽略错误
# 推荐
try:
risky_operation()
except SpecificError as e:
logger.error(f"操作失败: {e}")
# 或者采取恢复措施
8.3.3. 资源清理
# 不推荐
file = open("data.txt", "r")
try:
data = file.read()
process_data(data)
finally:
file.close()
# 推荐 - 使用 with 语句
with open("data.txt", "r") as file:
data = file.read()
process_data(data)
8.4.4. 异常日志记录
import logging
# 配置日志模块
logging.basicConfig(level=logging.ERROR)
class DatabaseError(Exception):
pass
class ValidationError(Exception):
pass
def get_user_from_db(user_id):
"""根据用户ID从数据库获取用户信息"""
if user_id == 0:
raise DatabaseError("数据库连接失败")
return {"user_id": user_id, "name": "Test User", "age": 25}
def validate_and_process(user_data):
"""验证用户数据的有效性"""
if not user_data.get("name") or user_data.get("age") < 0:
raise ValidationError("用户信息无效")
user_data["processed"] = True
return user_data
def process_user_data(user_id):
"""综合处理用户数据"""
try:
user_data = get_user_from_db(user_id)
result = validate_and_process(user_data)
return result
except DatabaseError as e:
logging.error(f"数据库错误 - 用户ID: {user_id}, 错误: {e}")
raise
except ValidationError as e:
logging.warning(f"数据验证失败 - 用户ID: {user_id}, 错误: {e}")
return None
except Exception as e:
logging.critical(f"未知错误 - 用户ID: {user_id}, 错误: {e}")
raise
process_user_data(1)
9.高级异常技巧
9.1.异常组 (Python 3.11+)
异常组(Exception Group)是 Python 3.11 新引入的特性,用于在同一代码块中同时抛出多个异常。
9.1.1.基本语法
def multiple_operations():
errors = []
try:
int("abc")
except ValueError as e:
errors.append(e)
try:
1 / 0
except ZeroDivisionError as e:
errors.append(e)
if errors:
raise ExceptionGroup("多个操作失败", errors)
# 使用 except* 语句分别处理不同类型的异常
try:
multiple_operations()
except* ValueError as eg:
print("处理ValueError:")
for e in eg.exceptions:
print(f" - {e}")
except* ZeroDivisionError as eg:
print("处理ZeroDivisionError:")
for e in eg.exceptions:
print(f" - {e}")
注意:
except*语法仅支持在 Python 3.11 及以上版本使用,且只能用于处理异常组。
9.2.异常调试信息
import traceback
try:
data = {"key": "value"}
print(data["nonexistent_key"])
except Exception as e:
print("错误信息:", str(e))
print("错误类型:", type(e).__name__)
print("追踪信息:")
traceback.print_exc() # 直接打印栈信息
# 获取详细的堆栈信息
tb_info = traceback.format_exc()
print("格式化堆栈:")
print(tb_info)
9.3.性能考虑
import time
# 方法1: 使用异常处理流程控制(不推荐)
def with_exceptions(n):
results = []
for i in range(n):
try:
if i % 2 == 0:
raise ValueError("测试异常")
results.append(i)
except ValueError:
pass
return results
# 方法2: 使用条件判断(推荐)
def without_exceptions(n):
results = []
for i in range(n):
if i % 2 != 0:
results.append(i)
return results
n = 10000
start = time.time()
with_exceptions(n)
exception_time = time.time() - start
start = time.time()
without_exceptions(n)
normal_time = time.time() - start
print(f"使用异常: {exception_time:.6f}秒")
print(f"使用条件: {normal_time:.6f}秒")
print(f"性能差异: {exception_time/normal_time:.2f}倍")
10.实际应用示例
10.1.Web 应用异常处理
from flask import Flask, jsonify
app = Flask(__name__)
# 定义自定义异常
class UserNotFoundError(Exception):
pass
class InvalidInputError(Exception):
pass
# 注册错误处理函数
@app.errorhandler(UserNotFoundError)
def handle_user_not_found(error):
return jsonify({
"error": "user_not_found",
"message": "用户不存在"
}), 404
@app.errorhandler(InvalidInputError)
def handle_invalid_input(error):
return jsonify({
"error": "invalid_input",
"message": "输入数据无效"
}), 400
@app.errorhandler(Exception)
def handle_generic_error(error):
return jsonify({
"error": "internal_server_error",
"message": "服务器内部错误"
}), 500
# 定义路由
@app.route('/users/<int:user_id>')
def get_user(user_id):
if user_id <= 0:
raise InvalidInputError("用户ID必须大于0")
user = get_user_from_database(user_id)
if not user:
raise UserNotFoundError(f"用户 {user_id} 不存在")
return jsonify(user)
def get_user_from_database(user_id):
if user_id == 1:
return {"id": 1, "name": "Alice"}
return None
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
10.2.数据验证框架
class DataValidator:
@staticmethod
def validate_email(email):
if not isinstance(email, str):
raise TypeError("邮箱必须是字符串")
if "@" not in email:
raise ValueError("无效的邮箱格式")
return email.lower()
@staticmethod
def validate_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0 or age > 150:
raise ValueError("年龄必须在0-150之间")
return age
@staticmethod
def validate_user_data(user_data):
errors = {}
try:
user_data['email'] = DataValidator.validate_email(user_data.get('email'))
except (TypeError, ValueError) as e:
errors['email'] = str(e)
try:
user_data['age'] = DataValidator.validate_age(user_data.get('age'))
except (TypeError, ValueError) as e:
errors['age'] = str(e)
if errors:
raise ValidationError("数据验证失败", errors)
return user_data
class ValidationError(Exception):
def __init__(self, message, errors):
self.message = message
self.errors = errors
super().__init__(self.message)
# 使用示例
user_input = {
'email': 'invalid-email',
'age': 200
}
try:
validated_data = DataValidator.validate_user_data(user_input)
print("数据验证成功:", validated_data)
except ValidationError as e:
print(f"验证失败: {e.message}")
for field, error in e.errors.items():
print(f" {field}: {error}")
11.总结
11.1.异常处理的核心原则
- 具体性: 捕获具体的异常类型
- 透明性: 不要静默忽略异常
- 资源管理: 使用 with 语句确保资源释放
- 层次性: 合理设计异常类层次结构
- 信息性: 提供有意义的错误信息
11.2.关键要点
- 使用
try-except-else-finally完整结构 - 自定义异常提高代码可读性
- 异常应用于异常情况,不要用于流程控制
- 合理使用异常链和异常组
- 记录异常信息便于调试
11.3.异常处理流程图
开始
↓
执行代码
↓
发生异常?
↓ 是
捕获异常
↓
处理异常
↓
继续执行
↓
结束
