首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >大部分人都不知道如何在Python中自定义异常

大部分人都不知道如何在Python中自定义异常

原创
作者头像
小白的大数据之旅
发布2025-07-21 11:16:15
发布2025-07-21 11:16:15
3850
举报

大部分人都不知道如何在 Python 中自定义异常

一、为啥要自定义异常?

先问个问题:你写代码的时候是不是经常遇到这种情况?程序报错了,但就一句干巴巴的 "Exception: error",根本不知道哪儿错了。比如你写个硬件监控程序,温度过高和电压不稳都报同一个错,排查起来头都大了。

这时候自定义异常就派上用场了。简单说,自定义异常就是给自己的程序量身定做错误类型,让错误信息更明白,调试更方便。

二、自定义异常基础操作

2.1 最基本的自定义异常

自定义异常其实特简单,就一句话:继承 Python 自带的 Exception 类。来看个例子:

代码语言:python
复制
# 最简单的自定义异常


class MyFirstError(Exception):


   pass


# 用一下试试


try:


   raise MyFirstError("这是我自定义的第一个异常")


except MyFirstError as e:


   print("捕获到异常:", e)

运行这段代码,会输出:捕获到异常: 这是我自定义的第一个异常。就这么简单,你已经创建了一个自己的异常类!

2.2 给异常加点料 ——init方法

光有个名字不够,咱们还得让异常携带更多信息。比如硬件错误,最好能带上温度、电压这些数据。这时候就需要重写init方法:

代码语言:python
复制
# 硬件错误异常,带温度信息


class HardwareError(Exception):


   def __init__(self, message, temperature=None):


       # 先调用父类的初始化方法,这步千万别忘!


       super().__init__(message)


       # 增加自定义属性


       self.temperature = temperature


# 用起来试试


try:


   # 模拟硬件过热


   raise HardwareError("CPU温度过高", temperature=137)


except HardwareError as e:


   print(f"错误信息:{e}")


   print(f"温度值:{e.temperature}℃")

运行结果:

代码语言:txt
复制
错误信息:CPU温度过高


温度值:137℃

看到没?这样报错不仅知道错哪儿了,还能拿到关键数据,调试的时候是不是方便多了?

2.3 自定义异常的继承关系

自定义异常也能搞继承,就像儿子继承老子的财产一样。比如硬件错误可以细分出 CPU 错误、内存错误,它们都属于硬件错误,但又有自己的特点。

代码语言:python
复制
# 硬件错误基类


class HardwareError(Exception):


   def __init__(self, message, device=None):


       super().__init__(message)


       self.device = device  # 出问题的设备


# CPU错误(继承硬件错误)


class CPUError(HardwareError):


   def __init__(self, message, device="CPU", temperature=None):


       super().__init__(message, device)


       self.temperature = temperature  # CPU特有:温度


# 内存错误(继承硬件错误)


class MemoryError(HardwareError):  # 注意:别和Python自带的MemoryError重名,这里只是举例


   def __init__(self, message, device="内存", used=None, total=None):


       super().__init__(message, device)


       self.used = used  # 内存特有:已使用


       self.total = total  # 内存特有:总容量

三、让错误信息说人话 —— 优化显示

默认的异常信息有时候太简陋了,比如上面的例子只会显示 "CPU 温度过高",但我们想让它直接显示 "CPU 温度过高:137℃",这时候就得重写str方法。

3.1 重写str方法

代码语言:python
复制
class HardwareError(Exception):


   def __init__(self, message, device=None):


       super().__init__(message)


       self.device = device


   # 重写__str__方法,定制显示格式


   def __str__(self):


       base_msg = super().__str__()  # 先拿到父类的错误信息


       if self.device:


           return f"【{self.device}错误】{base_msg}"

       return base_msg


class CPUError(HardwareError):


   def __init__(self, message, device="CPU", temperature=None):


       super().__init__(message, device)


       self.temperature = temperature


   def __str__(self):


       base_msg = super().__str__()  # 先拿到父类的显示


       if self.temperature:


           return f"{base_msg},当前温度:{self.temperature}℃"

       return base_msg


# 测试一下


try:


   raise CPUError("温度超过阈值", temperature=137)


except CPUError as e:


   print(e)  # 会显示:【CPU错误】温度超过阈值,当前温度:137℃

是不是清晰多了?团队协作的时候,别人看到这样的错误信息,一眼就知道问题出在哪儿。

3.2 不同异常显示对比

异常类型

默认显示

自定义str

普通 Exception

Exception: 出错了

出错了

自定义 HardwareError

HardwareError: 连接失败

【未知设备错误】连接失败

自定义 CPUError

CPUError: 温度过高

【CPU 错误】温度过高,当前温度:137℃

四、解决序列化难题 —— 让异常能存能取

有时候你需要把异常对象存起来(比如用 pickle 序列化),但自定义异常默认可能搞不定,这时候就得用reduce方法。

4.1 先看个失败的例子

代码语言:python
复制
import pickle


class CPUError(Exception):


   def __init__(self, message, temperature):


       super().__init__(message)


       self.temperature = temperature


# 创建异常对象


error = CPUError("过热", 137)


# 尝试序列化


try:


   data = pickle.dumps(error)


   # 反序列化


   new_error = pickle.loads(data)


   print(new_error.temperature)  # 这步可能会报错!


except Exception as e:


   print("序列化出错:", e)

为啥会出错?因为 pickle 在序列化对象时,需要知道怎么保存和恢复对象的状态。自定义异常如果有额外属性,默认的处理可能不够。

4.2 用reduce方法解决

代码语言:python
复制
import pickle


class CPUError(Exception):


   def __init__(self, message, temperature):


       super().__init__(message)


       self.temperature = temperature


   # 自定义序列化方法


   def __reduce__(self):


       # 返回一个元组:(构造函数, (参数1, 参数2, ...), 状态字典)


       return (self.__class__, (self.args[0], self.temperature), {})


# 再试一次


error = CPUError("过热", 137)


data = pickle.dumps(error)


new_error = pickle.loads(data)


print(new_error.temperature)  # 输出137,成功了!


print(new_error)  # 输出过热

reduce方法告诉 pickle:用哪个构造函数,传什么参数,以及需要保存的状态。这样序列化和反序列化就没问题了。

五、自定义异常中的多态 —— 一个接口,多种实现

多态是面向对象的重要概念,简单说就是:"同一个操作,作用在不同对象上,产生不同的结果"。在异常处理里,多态非常有用。

5.1 多态的实际例子

代码语言:python
复制
# 基类异常


class HardwareError(Exception):


   def __init__(self, message):


       super().__init__(message)


  


   def handle(self):


       """处理错误的方法,子类要重写"""

       raise NotImplementedError("子类必须实现handle方法")


# CPU错误


class CPUError(HardwareError):


   def __init__(self, message, temperature):


       super().__init__(message)


       self.temperature = temperature


  


   def handle(self):


       print(f"处理CPU错误:降低负载,当前温度{self.temperature}℃")


# 内存错误


class MemoryError(HardwareError):


   def __init__(self, message, used, total):


       super().__init__(message)


       self.used = used


       self.total = total


  


   def handle(self):


       print(f"处理内存错误:释放缓存,已用{self.used}/{self.total}")


# 统一处理函数


def handle_error(error):


   # 不管是CPUError还是MemoryError,都调用handle方法


   error.handle()


# 测试多态


try:


   raise CPUError("CPU过热", 137)


except HardwareError as e:


   handle_error(e)  # 输出:处理CPU错误:降低负载,当前温度137℃


try:


   raise MemoryError("内存不足", 80, 100)


except HardwareError as e:


   handle_error(e)  # 输出:处理内存错误:释放缓存,已用80/100

看到没?handle_error 函数根本不用管传入的是 CPUError 还是 MemoryError,只要是 HardwareError 的子类,就直接调用 handle 方法。这就是多态的好处:简化代码,增加扩展性。

5.2 多态的核心要素

  1. 继承:子类继承父类(如 CPUError 继承 HardwareError)
  2. 重写:子类重写父类的方法(如 handle 方法)
  3. 向上转型:用父类类型接收子类对象(如用 HardwareError 接收 CPUError 实例)

六、使用多态时的常见问题

6.1 常见错误 1:忘记重写父类方法

代码语言:python
复制
class HardwareError(Exception):


   def handle(self):


       raise NotImplementedError()


class DiskError(HardwareError):


   # 忘记重写handle方法


   pass


# 调用时会报错


try:


   raise DiskError("磁盘满了")


except HardwareError as e:


   e.handle()  # 会抛出NotImplementedError

解决办法:子类一定要重写父类中标记为必须重写的方法。

6.2 常见错误 2:类型判断破坏多态

代码语言:python
复制
def bad_handle_error(error):


   # 用type判断类型,破坏了多态


   if type(error) == CPUError:


       error.handle()


   elif type(error) == MemoryError:


       error.handle()


   else:


       print("无法处理")


# 问题:如果新增了DiskError,这个函数就处理不了了

正确做法:依赖父类接口,不关心具体类型。

6.3 常见错误 3:异常捕获顺序错误

代码语言:python
复制
try:


   raise CPUError("过热")


except HardwareError as e:  # 父类异常


   print("处理硬件错误")


except CPUError as e:  # 子类异常,永远不会被捕获


   print("处理CPU错误")

这是个大坑!Python 异常捕获是从上到下的,父类异常放在前面,子类异常就永远没机会被捕获了。正确的顺序是:先捕获子类异常,再捕获父类异常。

七、面试常问问题及答案

7.1 基础问题

问:为什么要自定义异常?

答:因为 Python 自带的异常太笼统了。比如一个硬件监控程序,CPU 过热和内存不足是完全不同的问题,用自定义异常能区分它们,让错误信息更具体,调试更方便,代码也更易维护。

问:自定义异常应该继承哪个类?

答:应该继承 Exception 类,而不是 BaseException。因为 BaseException 包括了一些特殊的异常(比如 KeyboardInterrupt),一般我们不希望自己的异常被这些特殊异常的处理逻辑捕获。

7.2 进阶问题

问:如何让自定义异常携带更多信息?

答:在init方法里添加属性,比如 CPUError 可以加个 temperature 属性。然后调用 super ().init() 初始化父类的信息,确保基础的错误信息也能保留。

问:pickle 序列化自定义异常时可能遇到什么问题?怎么解决?

答:如果自定义异常有额外的属性,默认序列化可能无法正确保存这些属性,导致反序列化后丢失数据。解决办法是重写reduce方法,告诉 pickle 如何保存和恢复对象的状态。

7.3 多态相关问题

问:什么是多态?在异常处理中怎么体现?

答:多态就是同一个操作(比如调用 handle 方法),作用在不同对象(CPUError、MemoryError)上,会有不同的结果。在异常处理中,我们可以用父类 HardwareError 接收所有子类异常,然后统一调用 handle 方法,每个子类会按自己的方式处理,这就是多态的体现。

问:使用多态时要注意什么?

答:首先子类要正确重写父类的方法;其次异常捕获要先子类后父类,不然子类异常会被父类捕获;还有不要用 type 判断具体类型,要依赖父类的接口,这样才能保持扩展性。

八、完整代码示例

最后给个综合示例,包含所有知识点:

代码语言:python
复制
import pickle


# 基类异常


class HardwareError(Exception):


   def __init__(self, message, device):


       super().__init__(message)


       self.device = device


   def __str__(self):


       return f"【{self.device}错误】{super().__str__()}"

   def handle(self):


       raise NotImplementedError("子类必须实现handle方法")


   def __reduce__(self):


       return (self.__class__, (self.args[0], self.device))


# CPU异常


class CPUError(HardwareError):


   def __init__(self, message, temperature):


       super().__init__(message, "CPU")


       self.temperature = temperature


   def __str__(self):


       base_str = super().__str__()


       return f"{base_str},温度:{self.temperature}℃"

   def handle(self):


       print(f"处理CPU错误:关闭部分进程,降低温度")


   def __reduce__(self):


       return (self.__class__, (self.args[0], self.temperature))


# 内存异常


class MemoryError(HardwareError):


   def __init__(self, message, used, total):


       super().__init__(message, "内存")


       self.used = used


       self.total = total


   def __str__(self):


       base_str = super().__str__()


       return f"{base_str},已用{self.used}MB/共{self.total}MB"

   def handle(self):


       print(f"处理内存错误:释放缓存,清理临时文件")


   def __reduce__(self):


       return (self.__class__, (self.args[0], self.used, self.total))


# 测试函数


def test_errors():


   # 测试异常多态


   errors = [


       CPUError("温度过高", 137),


       MemoryError("内存不足", 8000, 10000)


   ]


   for error in errors:


       try:


           raise error


       except HardwareError as e:


           print("捕获到错误:", e)


           e.handle()


           # 测试序列化


           data = pickle.dumps(e)


           new_e = pickle.loads(data)


           print("反序列化后:", new_e)


       print("-" * 50)


test_errors()

运行这段代码,你会看到:

  • 不同的异常有不同的显示信息
  • 调用同一个 handle 方法,处理方式不同(多态)
  • 序列化和反序列化正常工作

总结

自定义异常看起来简单,但里面门道不少。用好它能让你的代码更专业,错误处理更高效。多态作为面向对象的核心思想,在异常处理中尤其重要,能让代码更灵活、更易扩展。记住那些常见错误,面试的时候也能应对自如。

最后再强调一句:写代码时多想想 "如果出了这个错,我想知道哪些信息",按这个思路设计的自定义异常,肯定差不了!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为啥要自定义异常?
  • 二、自定义异常基础操作
    • 2.1 最基本的自定义异常
    • 2.2 给异常加点料 ——init方法
    • 2.3 自定义异常的继承关系
  • 三、让错误信息说人话 —— 优化显示
    • 3.1 重写str方法
    • 3.2 不同异常显示对比
  • 四、解决序列化难题 —— 让异常能存能取
    • 4.1 先看个失败的例子
    • 4.2 用reduce方法解决
  • 五、自定义异常中的多态 —— 一个接口,多种实现
    • 5.1 多态的实际例子
    • 5.2 多态的核心要素
  • 六、使用多态时的常见问题
    • 6.1 常见错误 1:忘记重写父类方法
    • 6.2 常见错误 2:类型判断破坏多态
    • 6.3 常见错误 3:异常捕获顺序错误
  • 七、面试常问问题及答案
    • 7.1 基础问题
    • 7.2 进阶问题
    • 7.3 多态相关问题
  • 八、完整代码示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档