Python异常处理最佳实践

一、异常处理基础

异常是程序运行时发生的错误,Python使用try-except机制处理异常。

1.1 基本语法

try:
result = 10 / 0
except ZeroDivisionError:
print("除数不能为0")

# 捕获多个异常
try:
file = open('nonexistent.txt')
data = int(file.read())
except FileNotFoundError:
print("文件不存在")
except ValueError:
print("无法转换为整数")

# 捕获多个异常到同一个处理器
try:
# 代码
pass
except (ValueError, TypeError) as e:
print(f"发生错误: {e}")

1.2 else和finally子句

try:
result = 10 / 2
except ZeroDivisionError:
print("除数不能为0")
else:
print(f"结果: {result}") # 没有异常时执行
finally:
print("清理资源") # 总是执行

二、异常层次结构

2.1 内置异常层次

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── StopIteration
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── AttributeError
├── ImportError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── NameError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
├── TypeError
└── ValueError

2.2 捕获异常的顺序

# 错误:子类在父类之后
try:
# 代码
pass
except Exception:
print("通用异常")
except ValueError: # 永远不会执行
print("值错误")

# 正确:子类在父类之前
try:
# 代码
pass
except ValueError:
print("值错误")
except Exception:
print("通用异常")

三、自定义异常

3.1 创建自定义异常

class ApplicationError(Exception):
"""应用程序错误基类"""
pass

class ValidationError(ApplicationError):
"""验证错误"""
pass

class DatabaseError(ApplicationError):
"""数据库错误"""
pass

# 使用
def validate_age(age):
if age < 0:
raise ValidationError("年龄不能为负数")
if age > 150:
raise ValidationError("年龄不合理")

3.2 带上下文的异常

class ValidationError(Exception):
def __init__(self, message, field=None, value=None):
super().__init__(message)
self.field = field
self.value = value

def __str__(self):
if self.field:
return f"{self.field}: {self.args[0]} (值: {self.value})"
return self.args[0]

# 使用
try:
raise ValidationError("无效的年龄", field="age", value=-5)
except ValidationError as e:
print(e) # age: 无效的年龄 (值: -5)
print(f"字段: {e.field}")
print(f"值: {e.value}")

四、异常链

4.1 使用raise from

def process_data(data):
try:
return int(data)
except ValueError as e:
raise TypeError("无法处理数据") from e

try:
process_data("invalid")
except TypeError as e:
print(f"错误: {e}")
print(f"原因: {e.__cause__}")

4.2 抑制异常链

try:
# 代码
pass
except Exception:
raise RuntimeError("新错误") from None # 不显示原始异常

五、异常处理的最佳实践

5.1 具体捕获异常

# 不好:捕获所有异常
try:
result = process_data()
except:
print("出错了")

# 好:捕获具体异常
try:
result = process_data()
except ValueError as e:
print(f"值错误: {e}")
except TypeError as e:
print(f"类型错误: {e}")

5.2 不要吞掉异常

# 不好:静默失败
try:
risky_operation()
except Exception:
pass # 什么都不做

# 好:至少记录日志
import logging

try:
risky_operation()
except Exception as e:
logging.error(f"操作失败: {e}", exc_info=True)
raise # 重新抛出

5.3 使用上下文管理器

# 不好:手动管理资源
file = None
try:
file = open('data.txt')
data = file.read()
except IOError as e:
print(f"读取失败: {e}")
finally:
if file:
file.close()

# 好:使用上下文管理器
try:
with open('data.txt') as file:
data = file.read()
except IOError as e:
print(f"读取失败: {e}")

六、EAFP vs LBYL

6.1 EAFP(Easier to Ask for Forgiveness than Permission)

# Python风格:先尝试,失败再处理
try:
value = my_dict['key']
except KeyError:
value = default_value

# 文件操作
try:
with open('file.txt') as f:
data = f.read()
except FileNotFoundError:
data = create_default_data()

6.2 LBYL(Look Before You Leap)

# 不推荐:先检查再操作
if 'key' in my_dict:
value = my_dict['key']
else:
value = default_value

# 文件操作
import os
if os.path.exists('file.txt'):
with open('file.txt') as f:
data = f.read()
else:
data = create_default_data()

6.3 何时使用LBYL

# 当检查成本低且操作成本高时
if user.is_authenticated(): # 简单检查
perform_expensive_operation() # 昂贵操作

七、异常处理模式

7.1 重试模式

import time

def retry(max_attempts=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"尝试 {attempt + 1} 失败: {e}")
time.sleep(delay)
return wrapper
return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
# 可能失败的操作
pass

7.2 回退模式

def get_config():
"""尝试多个配置源"""
try:
return load_from_file('config.json')
except FileNotFoundError:
try:
return load_from_env()
except KeyError:
return get_default_config()

7.3 转换模式

class APIError(Exception):
pass

def api_call():
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
# 转换为应用程序异常
raise APIError(f"API调用失败: {e}") from e

八、异常处理的反模式

8.1 过度捕获

# 不好:捕获太宽泛
try:
result = complex_operation()
except Exception:
return None

# 好:只捕获预期的异常
try:
result = complex_operation()
except (ValueError, TypeError) as e:
logging.error(f"操作失败: {e}")
return None

8.2 使用异常控制流程

# 不好:用异常控制正常流程
def find_item(items, target):
try:
return items[items.index(target)]
except ValueError:
return None

# 好:使用正常的控制结构
def find_item(items, target):
for item in items:
if item == target:
return item
return None

8.3 空except块

# 不好
try:
risky_operation()
except:
pass

# 好
try:
risky_operation()
except SpecificError as e:
logging.warning(f"操作失败,使用默认值: {e}")
use_default()

九、日志记录

9.1 记录异常信息

import logging

try:
result = process_data()
except Exception as e:
# 记录异常和堆栈跟踪
logging.exception("处理数据时发生错误")
# 或
logging.error("处理数据时发生错误", exc_info=True)

9.2 结构化日志

import logging

try:
user_id = 123
result = process_user(user_id)
except Exception as e:
logging.error(
"处理用户失败",
extra={
'user_id': user_id,
'error_type': type(e).__name__,
'error_message': str(e)
},
exc_info=True
)

十、测试异常

10.1 使用pytest测试异常

import pytest

def divide(a, b):
if b == 0:
raise ValueError("除数不能为0")
return a / b

def test_divide_by_zero():
with pytest.raises(ValueError) as exc_info:
divide(10, 0)

assert "除数不能为0" in str(exc_info.value)

10.2 测试异常消息

def test_validation_error():
with pytest.raises(ValidationError, match=r"年龄.*负数"):
validate_age(-5)

十一、异常处理的性能

11.1 异常的成本

import timeit

# 使用异常(慢)
def with_exception():
try:
return int("invalid")
except ValueError:
return 0

# 使用检查(快)
def with_check(s):
if s.isdigit():
return int(s)
return 0

# 性能对比
print(timeit.timeit(with_exception, number=100000))
print(timeit.timeit(lambda: with_check("invalid"), number=100000))

11.2 何时避免异常

# 频繁操作时避免异常
# 不好:每次都可能抛出异常
for item in large_list:
try:
value = int(item)
except ValueError:
value = 0

# 好:预先过滤
valid_items = [item for item in large_list if item.isdigit()]
values = [int(item) for item in valid_items]

十二、实战案例:API错误处理

class APIClient:
class APIError(Exception):
def __init__(self, message, status_code=None, response=None):
super().__init__(message)
self.status_code = status_code
self.response = response

class RateLimitError(APIError):
pass

class AuthenticationError(APIError):
pass

def request(self, method, url, **kwargs):
try:
response = requests.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except requests.HTTPError as e:
if e.response.status_code == 429:
raise self.RateLimitError(
"超过速率限制",
status_code=429,
response=e.response
) from e
elif e.response.status_code == 401:
raise self.AuthenticationError(
"认证失败",
status_code=401,
response=e.response
) from e
else:
raise self.APIError(
f"HTTP错误: {e}",
status_code=e.response.status_code,
response=e.response
) from e
except requests.RequestException as e:
raise self.APIError(f"请求失败: {e}") from e

十三、实战案例:数据验证

class ValidationError(Exception):
def __init__(self, errors):
self.errors = errors
super().__init__(self._format_errors())

def _format_errors(self):
return "; ".join(f"{field}: {error}" for field, error in self.errors.items())

def validate_user(data):
errors = {}

if not data.get('username'):
errors['username'] = "用户名不能为空"
elif len(data['username']) < 3:
errors['username'] = "用户名至少3个字符"

if not data.get('email'):
errors['email'] = "邮箱不能为空"
elif '@' not in data['email']:
errors['email'] = "邮箱格式不正确"

if errors:
raise ValidationError(errors)

return data

# 使用
try:
user = validate_user({'username': 'ab', 'email': 'invalid'})
except ValidationError as e:
print(f"验证失败: {e}")
for field, error in e.errors.items():
print(f" {field}: {error}")

十四、异常处理检查清单

1. 只捕获你能处理的异常
2. 不要吞掉异常
3. 使用具体的异常类型
4. 记录异常信息
5. 清理资源(使用finally或上下文管理器)
6. 考虑异常链
7. 为库代码定义自定义异常
8. 不要用异常控制正常流程
9. 测试异常情况
10. 文档化可能抛出的异常

十五、总结

异常处理是编写健壮Python代码的关键。遵循EAFP原则,使用具体的异常类型,合理使用自定义异常,并确保异常被正确记录和处理。避免过度捕获和静默失败,使用上下文管理器管理资源。通过良好的异常处理实践,可以使代码更加可靠和易于维护。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐