文章目录
- 简单说,异常是程序运行时的"错误信号"。当Python遇到无法处理的情况(如除以零、访问不存在的列表元素),就会抛出异常。 # 常见内置异常示例 result = 10 / 0 # ZeroDivisionError lst = [1, 2] print(lst[3]) # IndexError
- try: # 可能出错的代码 file = open(“non_existent.txt”) except FileNotFoundError: # 处理特定异常 print(“文件不存在,已创建新文件”) file = open(“non_existent.txt”, “w”) else: # 无异常时执行 print(“文件操作成功”) finally: # 无论是否异常都执行 file.close()
- class BalanceInsufficientError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f”余额不足:当前余额{balance},需{amount}”) # 使用示例 def withdraw(balance, amount): if balance < amount: raise BalanceInsufficientError(balance, amount) return balance – amount try: withdraw(500, 1000) except BalanceInsufficientError as e: print(e) # 余额不足:当前余额500,需1000
- Python异常是层次化的,合理设计继承关系: BaseException└── Exception├── BankError (自定义基类)│ ├── BalanceInsufficientError│ └── InvalidAccountError└── NetworkError (自定义基类)├── ConnectionTimeoutError└── DNSResolutionError 示例实现: class BankError(Exception): “””银行系统基础异常””” pass class BalanceInsufficientError(BankError): “””余额不足异常””” pass class InvalidAccountError(BankError): “””无效账户异常””” pass
- 让异常对象更智能: class TemperatureError(Exception): def __init__(self, temp, unit=”C”): self.temp = temp self.unit = unit super().__init__(f”温度异常: {temp}°{unit}”) def to_fahrenheit(self): if self.unit.upper() == “C”: return (self.temp * 9/5) + 32 return self.temp # 假设已经是华氏度 # 使用示例 try: if TemperatureError(-300).temp < -273.15: raise TemperatureError(-300) except TemperatureError as e: print(e) # 温度异常: -300°C print(f”华氏度: {e.to_fahrenheit():.1f}°F”)
- 使用Error后缀(如InvalidInputError) 避免使用Exception后缀(这是基类) 保持类名清晰描述问题
- 业务逻辑错误(如"库存不足") 需要携带额外上下文信息 需要区分不同错误类型
- 简单脚本 错误不会在多个地方处理 错误信息足够明确
- class NegativeAgeError(Exception): “””当尝试设置负年龄时抛出 Attributes: age (int): 尝试设置的非法年龄值 “”” def __init__(self, age): self.age = age super().__init__(f”年龄不能为负数: {age}”)
- import logging class DataProcessingError(Exception): pass def process_data(data): try: if not data: raise DataProcessingError(“空数据集”) # 处理数据… except DataProcessingError as e: logging.error(f”数据处理失败: {str(e)}”, exc_info=True) raise # 可选择重新抛出
- class UserRegistrationError(Exception): pass class UsernameTooShortError(UserRegistrationError): def __init__(self, username): self.username = username super().__init__(f”用户名'{username}’太短,至少需要4个字符”) class PasswordWeakError(UserRegistrationError): pass def register_user(username, password): if len(username) < 4: raise UsernameTooShortError(username) if len(password) < 8: raise PasswordWeakError(“密码至少需要8个字符”) # 实际注册逻辑… try: register_user(“ab”, “123”) except UserRegistrationError as e: print(f”注册失败: {e}”)
- class APIError(Exception): “””API请求基础异常””” pass class HTTPStatusError(APIError): def __init__(self, status_code, response): self.status_code = status_code self.response = response super().__init__(f”HTTP错误: {status_code}”) class TimeoutError(APIError): pass def fetch_data(url, timeout=5): try: # 模拟请求 import random if random.random() < 0.3: raise TimeoutError(“请求超时”) status = random.choice([200, 404, 500]) if status != 200: raise HTTPStatusError(status, {“url”: url}) return {“data”: “success”} except TimeoutError: raise except Exception as e: raise APIError(f”未知API错误: {str(e)}”) # 使用示例 try: result = fetch_data(“https://api.example.com”) except APIError as e: if isinstance(e, HTTPStatusError): print(f”HTTP错误: {e.status_code}, 响应: {e.response}”) elif isinstance(e, TimeoutError): print(“请求超时,请重试”) else: print(f”API错误: {e}”)
- try: 1 / 0 except ZeroDivisionError: raise ValueError(“无效计算”) from None # 隐藏原始异常
- try: raise KeyError(“原始错误”) except KeyError as e: try: raise ValueError(“包装错误”) from e except ValueError as new_e: print(new_e.__cause__) # 原始错误: KeyError(‘原始错误’)
- 明确异常关系: def process_file(path): try: with open(path) as f: content = f.read() except OSError as e: raise RuntimeError(f”无法处理文件 {path}”) from e
目录
- 引言:为什么需要自定义异常
- 一、异常基础:先搞懂Python的异常体系
- 1.1 异常是什么?
- 1.2 异常处理三件套
- 二、自定义异常入门:三步创建你的第一个异常
- 2.1 最简单的自定义异常
- 2.2 为什么需要自定义异常?
- 三、进阶技巧:让异常更专业
- 3.1 添加初始化参数
- 3.2 继承链设计
- 3.3 添加实用方法
- 四、最佳实践:这样用才专业
- 4.1 命名规范
- 4.2 何时使用自定义异常
- 4.3 何时避免自定义异常
- 4.4 文档字符串很重要
- 4.5 与logging结合
- 五、真实场景案例分析
- 案例1:用户注册系统
- 案例2:API请求封装
- 六、常见误区与解决方案
- 七、性能考量:异常不是控制流
- 不推荐的做法(用异常控制循环)
- 推荐做法
- 八、Python 3的异常增强特性
- 8.1 异常链(Exception Chaining)
- 8.2 cause__和__context
- 8.3 raise … from语法
- 九、总结:自定义异常的核心价值
想象你正在开发一个银行系统,用户转账时余额不足。如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足。又或者你正在构建一个电商系统,当库存不足时,一个通用的Exception会让问题排查变得困难重重。
这正是Python自定义异常的价值所在——它让错误处理更精准、代码更易维护、调试更高效。本文将用最接地气的方式,带你掌握这个实用技能。
简单说,异常是程序运行时的"错误信号"。当Python遇到无法处理的情况(如除以零、访问不存在的列表元素),就会抛出异常。
# 常见内置异常示例 result = 10 / 0 # ZeroDivisionError lst = [1, 2] print(lst[3]) # IndexError
try:
# 可能出错的代码
file = open("non_existent.txt")
except FileNotFoundError:
# 处理特定异常
print("文件不存在,已创建新文件")
file = open("non_existent.txt", "w")
else:
# 无异常时执行
print("文件操作成功")
finally:
# 无论是否异常都执行
file.close()
只需继承Exception基类:
class MyError(Exception):
pass
# 使用示例
def check_value(x):
if x > 100:
raise MyError("值不能超过100")
return x
try:
check_value(150)
except MyError as e:
print(f"捕获到自定义错误: {e}")
输出:
捕获到自定义错误: 值不能超过100
- 精准定位问题:区分不同业务错误
- 更好的文档:异常类名本身就是文档
- 灵活处理:可以添加额外信息
- 统一风格:保持项目代码一致性
class BalanceInsufficientError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额不足:当前余额{balance},需{amount}")
# 使用示例
def withdraw(balance, amount):
if balance < amount:
raise BalanceInsufficientError(balance, amount)
return balance - amount
try:
withdraw(500, 1000)
except BalanceInsufficientError as e:
print(e) # 余额不足:当前余额500,需1000
Python异常是层次化的,合理设计继承关系:
BaseException
└── Exception
├── BankError (自定义基类)
│ ├── BalanceInsufficientError
│ └── InvalidAccountError
└── NetworkError (自定义基类)
├── ConnectionTimeoutError
└── DNSResolutionError
示例实现:
class BankError(Exception):
"""银行系统基础异常"""
pass
class BalanceInsufficientError(BankError):
"""余额不足异常"""
pass
class InvalidAccountError(BankError):
"""无效账户异常"""
pass
让异常对象更智能:
class TemperatureError(Exception):
def __init__(self, temp, unit="C"):
self.temp = temp
self.unit = unit
super().__init__(f"温度异常: {temp}°{unit}")
def to_fahrenheit(self):
if self.unit.upper() == "C":
return (self.temp * 9/5) + 32
return self.temp # 假设已经是华氏度
# 使用示例
try:
if TemperatureError(-300).temp < -273.15:
raise TemperatureError(-300)
except TemperatureError as e:
print(e) # 温度异常: -300°C
print(f"华氏度: {e.to_fahrenheit():.1f}°F")
- 使用Error后缀(如InvalidInputError)
- 避免使用Exception后缀(这是基类)
- 保持类名清晰描述问题
- 业务逻辑错误(如"库存不足")
- 需要携带额外上下文信息
- 需要区分不同错误类型
- 简单脚本
- 错误不会在多个地方处理
- 错误信息足够明确
class NegativeAgeError(Exception):
"""当尝试设置负年龄时抛出
Attributes:
age (int): 尝试设置的非法年龄值
"""
def __init__(self, age):
self.age = age
super().__init__(f"年龄不能为负数: {age}")
import logging
class DataProcessingError(Exception):
pass
def process_data(data):
try:
if not data:
raise DataProcessingError("空数据集")
# 处理数据...
except DataProcessingError as e:
logging.error(f"数据处理失败: {str(e)}", exc_info=True)
raise # 可选择重新抛出
class UserRegistrationError(Exception):
pass
class UsernameTooShortError(UserRegistrationError):
def __init__(self, username):
self.username = username
super().__init__(f"用户名'{username}'太短,至少需要4个字符")
class PasswordWeakError(UserRegistrationError):
pass
def register_user(username, password):
if len(username) < 4:
raise UsernameTooShortError(username)
if len(password) < 8:
raise PasswordWeakError("密码至少需要8个字符")
# 实际注册逻辑...
try:
register_user("ab", "123")
except UserRegistrationError as e:
print(f"注册失败: {e}")
class APIError(Exception):
"""API请求基础异常"""
pass
class HTTPStatusError(APIError):
def __init__(self, status_code, response):
self.status_code = status_code
self.response = response
super().__init__(f"HTTP错误: {status_code}")
class TimeoutError(APIError):
pass
def fetch_data(url, timeout=5):
try:
# 模拟请求
import random
if random.random() < 0.3:
raise TimeoutError("请求超时")
status = random.choice([200, 404, 500])
if status != 200:
raise HTTPStatusError(status, {"url": url})
return {"data": "success"}
except TimeoutError:
raise
except Exception as e:
raise APIError(f"未知API错误: {str(e)}")
# 使用示例
try:
result = fetch_data("https://api.example.com")
except APIError as e:
if isinstance(e, HTTPStatusError):
print(f"HTTP错误: {e.status_code}, 响应: {e.response}")
elif isinstance(e, TimeoutError):
print("请求超时,请重试")
else:
print(f"API错误: {e}")
误区1:过度使用自定义异常
问题:为每个小错误都创建异常类,导致代码膨胀
解决:遵循"足够好"原则,只在需要区分错误类型或携带额外信息时创建
误区2:异常类设计混乱
问题:继承关系不合理,导致捕获困难
解决:提前设计异常层次结构,保持逻辑清晰
误区3:忽略异常信息
问题:抛出异常时不提供足够上下文
解决:始终包含有意义的错误信息,如:
# 不好的做法
raise FileNotFoundError
# 好的做法
raise FileNotFoundError("配置文件config.ini未找到")
误区4:异常处理过于宽泛
问题:捕获所有异常导致隐藏bug
解决:尽可能捕获特定异常
# 不好的做法
try:
# 代码
except Exception:
# 处理
# 好的做法
try:
# 代码
except ValueError:
# 处理值错误
except IOError:
# 处理IO错误
虽然Python异常处理很高效,但不应滥用:
python
def find_in_list(lst, target):
try:
while True:
item = next(lst) # 假设lst是迭代器
if item == target:
return item
except StopIteration:
return None
def find_in_list(lst, target):
for item in lst:
if item == target:
return item
return None
try:
1 / 0
except ZeroDivisionError:
raise ValueError("无效计算") from None # 隐藏原始异常
try:
raise KeyError("原始错误")
except KeyError as e:
try:
raise ValueError("包装错误") from e
except ValueError as new_e:
print(new_e.__cause__) # 原始错误: KeyError('原始错误')
明确异常关系:
def process_file(path):
try:
with open(path) as f:
content = f.read()
except OSError as e:
raise RuntimeError(f"无法处理文件 {path}") from e
- 代码更清晰:异常类名本身就是文档
- 错误处理更精准:区分不同错误场景
- 调试更高效:携带丰富的上下文信息
- API更友好:提供明确的错误提示
记住:好的异常设计应该像交通信号灯——清晰明确地指示程序状态,帮助开发者快速定位问题。
以上就是Python自定义异常的全面指南(入门到实践)的详细内容,更多关于Python自定义异常的资料请关注风君子博客其它相关文章!
您可能感兴趣的文章:
- Python中try用法、内置异常类型与自定义异常类型拓展案例详解
- 如何在Python中自定义异常类与异常处理机制
- Python中的异常处理以及自定义异常类型方式
- python中自定义异常/raise关键字抛出异常的案例解析
- python如何使用raise抛出自定义异常