跳到主要内容

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()

小结

本章我们学习了:

  1. 常见异常类型
  2. try-except 结构捕获异常
  3. else 和 finally 子句
  4. 主动抛出异常(raise)
  5. 自定义异常类
  6. 异常链
  7. 断言
  8. 上下文管理器
  9. 异常处理最佳实践

练习

  1. 编写一个除法计算器,处理各种异常
  2. 创建一个学生信息管理系统,验证输入数据的合法性
  3. 实现一个文件读取函数,处理各种文件相关异常
  4. 使用上下文管理器实现一个计时器