Python 异常处理
异常是程序运行时发生的错误。本章将介绍如何处理异常。
什么是异常?
当程序出现错误时,Python 会抛出异常。如果不处理,程序会终止。
print(1 / 0) # ZeroDivisionError: division by zero
# 列表越界
my_list = [1, 2, 3]
print(my_list[10]) # IndexError: list index out of range
# 类型错误
print("hello" + 123) # TypeError: can only concatenate str (not "int") to str
常见异常类型
| 异常类型 | 描述 |
|---|---|
| SyntaxError | 语法错误 |
| NameError | 名称错误(变量未定义) |
| TypeError | 类型错误 |
| ValueError | 值错误 |
| IndexError | 索引错误 |
| KeyError | 键错误 |
| ZeroDivisionError | 除零错误 |
| FileNotFoundError | 文件不存在 |
| ImportError | 导入错误 |
| AttributeError | 属性错误 |
try-except 结构
基本语法
try:
# 可能发生异常的代码
result = 10 / 0
except ZeroDivisionError:
# 处理特定异常
print("除数不能为零")
多个 except
try:
# 可能发生多种异常的代码
num = int(input("请输入一个数字:"))
result = 10 / num
except ValueError:
print("请输入有效的数字")
except ZeroDivisionError:
print("除数不能为零")
捕获所有异常
try:
# 可能发生异常的代码
result = 10 / 0
except Exception as e:
print(f"发生错误:{e}")
访问异常信息
try:
result = 10 / 0
except Exception as e:
print(f"异常类型:{type(e).__name__}")
print(f"异常信息:{e}")
print(f"异常描述:{e.args}")
else 子句
如果 try 块没有发生异常,执行 else 块:
try:
num = int(input("请输入一个数字:"))
result = 10 / num
except ValueError:
print("请输入有效的数字")
except ZeroDivisionError:
print("除数不能为零")
else:
print(f"结果是:{result}")
finally 子句
无论是否发生异常,都会执行 finally 块:
try:
file = open("test.txt", "r")
content = file.read()
except FileNotFoundError:
print("文件不存在")
finally:
# 清理工作
if 'file' in locals():
file.close()
print("文件已关闭")
主动抛出异常
raise 语句
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f"捕获异常:{e}")
自定义异常
class MyError(Exception):
"""自定义异常类"""
def __init__(self, message):
self.message = message
super().__init__(self.message)
# 使用自定义异常
def validate_age(age):
if age < 0:
raise MyError("年龄不能为负数")
if age > 150:
raise MyError("年龄超出合理范围")
return True
try:
validate_age(-5)
except MyError as e:
print(f"自定义异常:{e.message}")
异常链
异常传播
异常可以向上传播:
def level3():
return 1 / 0
def level2():
return level3()
def level1():
try:
result = level2()
except Exception as e:
print(f"在 level1 捕获:{e}")
raise # 重新抛出异常
try:
level1()
except Exception as e:
print(f"最终捕获:{e}")
异常链(Python 3)
使用 raise ... from 创建异常链:
try:
raise ValueError("原始错误")
except ValueError as e:
raise TypeError("新的错误") from e
断言
使用 assert 进行调试断言:
def divide(a, b):
assert b != 0, "除数不能为零"
return a / b
print(divide(10, 2)) # 5.0
print(divide(10, 0)) # AssertionError: 除数不能为零
上下文管理器
with 语句
自动管理资源:
# 文件操作
with open("test.txt", "r") as file:
content = file.read()
# 文件在这里会自动关闭
# 锁操作
from threading import Lock
lock = Lock()
with lock:
# 临界区代码
print("获取了锁")
# 锁会自动释放
自定义上下文管理器
class MyContext:
def __enter__(self):
print("进入上下文")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("退出上下文")
if exc_type is not None:
print(f"发生异常:{exc_type.__name__}")
return False # 不阻止异常传播
with MyContext() as ctx:
print("在上下文中")
# 可以在这里抛出异常
# raise ValueError("测试异常")
使用 contextmanager 装饰器
from contextlib import contextmanager
@contextmanager
def my_context():
print("进入")
try:
yield "资源"
finally:
print("退出")
with my_context() as resource:
print(f"使用{resource}")
最佳实践
1. 尽量具体地捕获异常
# 不推荐
try:
result = some_function()
except:
pass
# 推荐
try:
result = some_function()
except SpecificException as e:
handle_error(e)
2. 不要过度使用异常
# 不推荐:使用异常控制流程
try:
result = data["key"]
except KeyError:
result = default_value
# 推荐:使用 dict 方法
result = data.get("key", default_value)
3. 记录异常日志
import logging
logging.basicConfig(level=logging.ERROR)
try:
result = some_function()
except Exception as e:
logging.error(f"发生错误:{e}", exc_info=True)
4. 清理资源
# 使用 try-finally
file = None
try:
file = open("test.txt", "r")
content = file.read()
finally:
if file:
file.close()
# 使用 with 语句(推荐)
with open("test.txt", "r") as file:
content = file.read()
小结
本章我们学习了:
- 常见异常类型
- try-except 结构捕获异常
- else 和 finally 子句
- 主动抛出异常(raise)
- 自定义异常类
- 异常链
- 断言
- 上下文管理器
- 异常处理最佳实践
练习
- 编写一个除法计算器,处理各种异常
- 创建一个学生信息管理系统,验证输入数据的合法性
- 实现一个文件读取函数,处理各种文件相关异常
- 使用上下文管理器实现一个计时器