Python 错误处理高级编程
在掌握了 Python 错误处理的基础知识后,我们可以进一步探索更高级的技巧。这些技巧能够帮助你编写更灵活、更可维护的代码。本文将深入讲解自定义异常类、异常的嵌套与链式处理、抛出异常、上下文管理器以及日志记录与错误跟踪。
错误处理的设计原则
有效的错误处理应遵循以下原则:
- 明确性:捕获特定异常,避免使用通用的except Exception。
- 最小化:仅在必要时捕获异常,避免过度使用try-except。
- 可读性:提供清晰的错误消息,便于用户和开发者理解。
- 可恢复性:设计程序以从错误中恢复或优雅退出。
自定义异常类
当内置异常类型无法满足特定需求时,可以通过继承Exception
类创建自定义异常。自定义异常可以携带额外信息,使错误处理更具语义化。
以下是一个定义和使用自定义异常的示例:
class InvalidInputError(Exception):
def __init__(self, message, value):
self.message = message
self.value = value
super().__init__(self.message)
def validate_number(num):
if not isinstance(num, (int, float)) or num < 0:
raise InvalidInputError("Input must be a non-negative number", num)
return num * 2
try:
result = validate_number(-5)
print(f"Result: {result}")
except InvalidInputError as e:
print(f"Error: {e.message}, Invalid value: {e.value}")
输出:
Error: Input must be a non-negative number, Invalid value: -5
在这个例子中,InvalidInputError
类增加了value
属性来存储无效输入,便于调试和错误报告。自定义异常让代码更具可读性和针对性。
异常的嵌套与链式处理
在复杂程序中,异常可能在多个层次中发生。嵌套try-except
块可以处理不同层次的错误,而异常链(通过raise ... from
)可以保留原始异常的上下文。
以下是一个展示嵌套异常和异常链的示例:
class DatabaseError(Exception):
pass
def fetch_data(query):
if not query:
raise ValueError("Query cannot be empty")
def process_query(query):
try:
fetch_data(query)
except ValueError as e:
raise DatabaseError("Failed to process query") from e
try:
process_query("")
except DatabaseError as e:
print(f"Error: {e}")
print(f"Caused by: {e.__cause__}")
输出:
Error: Failed to process query
Caused by: Query cannot be empty
在这个例子中,fetch_data
抛出ValueError
,process_query
捕获后抛出DatabaseError
,并通过from
保留了原始异常信息。e.__cause__
显示了异常链的根源,便于追溯问题。
使用raise抛出异常
raise
语句可以主动抛出异常,用于在特定条件下中断程序或传递错误信息。你可以抛出内置异常或自定义异常。
以下是一个主动抛出异常的示例:
def divide_with_limit(a, b, limit):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
result = a / b
if result > limit:
raise ValueError(f"Result {result} exceeds limit {limit}")
return result
try:
result = divide_with_limit(100, 2, 40)
print(f"Result: {result}")
except (ZeroDivisionError, ValueError) as e:
print(f"Error: {e}")
输出:
Error: Result 50.0 exceeds limit 40
这个例子展示了如何在特定条件下(如结果超过限制)抛出异常。使用元组(ZeroDivisionError, ValueError)
可以同时捕获多种异常类型。
上下文管理器与错误处理
上下文管理器(通过with
语句使用)可以简化资源管理和错误处理,特别是在处理文件、数据库连接等需要清理的资源时。contextlib
模块提供了创建自定义上下文管理器的工具。
以下是一个使用上下文管理器处理文件操作的示例:
from contextlib import contextmanager
@contextmanager
def safe_file_writer(filename):
file = None
try:
file = open(filename, 'w')
yield file
except Exception as e:
print(f"Error writing to {filename}: {e}")
finally:
if file:
file.close()
print(f"File {filename} closed")
try:
with safe_file_writer("output.txt") as f:
f.write("Hello, World!")
raise ValueError("Simulated error")
except ValueError as e:
print(f"Caught error: {e}")
输出:
Error writing to output.txt: Simulated error
File output.txt closed
在这个例子中,safe_file_writer
上下文管理器确保文件在写入错误后也能正确关闭。yield
将控制权交给with
块,finally
保证资源清理。
日志记录与错误跟踪
在生产环境中,仅仅打印错误信息不足以调试复杂问题。Python的logging
模块可以记录详细的错误信息,包括时间戳、错误级别和堆栈跟踪。
以下是一个使用logging
记录错误的示例:
import logging
logging.basicConfig(
filename='app.log',
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def process_data(data):
try:
result = 100 / data
return result
except ZeroDivisionError as e:
logging.error("Division by zero error", exc_info=True)
return None
result = process_data(0)
if result is None:
print("Operation failed, check log for details")
输出(控制台):
Operation failed, check log for details
输出(app.log
文件):
2025-07-24 10:29:36,682 - ERROR - Division by zero error
Traceback (most recent call last):
File "/Users/yzy/programs/hello_py312/main.py", line 11, in process_data
result = 100 / data
~~~~^~~~~~
ZeroDivisionError: division by zero
这个例子使用logging.error
记录异常的完整堆栈信息,exc_info=True
确保包含追溯信息。日志文件便于长期跟踪和分析错误。
总结
本篇文章我们学习了Python高级错误处理技巧,包括:
- 创建和使用自定义异常类
- 处理嵌套异常和异常链
- 主动抛出异常以控制程序流程
- 使用上下文管理器简化资源管理和错误处理
- 使用
logging
模块记录详细的错误信息