PAYATHON 2026

Python 库设计中,何时使用自定义异常或内置异常?

数据阿明

建议混合使用:文件不存在时使用 FileNotFoundError;视频打开、读取和标定等领域失败使用自定义异常。是否需要新增异常类型,主要看调用方是否需要根据类型采取不同的处理措施。

要求调用方引用自定义异常是正常的 API 设计。不要在出口把这些异常统一改成普通 Exception,否则调用方将无法按类型处理错误。

如何为当前场景选择异常

原有的两种方案分别将多种视频问题归入 VideoProcessingError 或 ValueError。前者便于捕获视频相关失败;后者虽然更常见,却可能同时捕获标定算法、参数转换等位置抛出的无关 ValueError。

可以先采用以下约定:

情况建议异常设计依据
本地视频文件不存在FileNotFoundError已有含义明确的内置类型
参数类型不受支持TypeError表达调用参数的类型问题
参数类型正确,但取值不符合约定ValueError表达参数值问题
视频无法打开或无法取得所需帧VideoProcessingError表达视频处理领域的失败
检测结果不足,无法完成标定CalibrationError调用方可能需要更换素材或调整标定条件

这是针对当前问题的设计建议,Python 并没有强制规定。如果 API 明确将不可用的视频定义为无效输入,也可以使用 ValueError。重要的是保持语义一致,并在文档中说明。

异常类型应根据调用方的处理方式来设计。如果打开失败和读取失败都只需要提示用户换一个视频,一个 VideoProcessingError 就够用。调用方确实需要分别处理时,再增加 VideoOpenError、VideoReadError 等子类。

自定义异常需要怎样的继承关系

可以为库的领域异常提供一个公共父类:

class MylibError(Exception):
    pass


class VideoProcessingError(MylibError):
    pass


class CalibrationError(MylibError):
    pass

调用方可以分别捕获视频失败和标定失败,也可以用 except MylibError 处理这组预期的领域失败。

在这个方案中,FileNotFoundError 不属于 MylibError。如果调用方要统一处理文件缺失和领域失败,可以捕获异常元组:

except (FileNotFoundError, mylib.MylibError):
    ...

文档中需要说明这一点,避免用户误以为 MylibError 能捕获库执行过程中出现的所有异常。

VideoProcessingError(ValueError) 在语法上可行,捕获 ValueError 时也会捕获它。但继承关系也表达语义上的归属:只有所有 VideoProcessingError 都确实表示无效参数值时,这种关系才合适。解码后端失败或处理过程中的读取失败,未必符合这个含义。

优先采用简单的单继承结构。Python 官方文档建议避免同时继承多个异常类型,因为可能涉及 args 处理冲突和底层内存布局兼容问题。Python 内置异常文档

视频校验可以怎样实现

下例假设输入是本地文件路径,并要求视频至少能提供一帧。示例只展示异常定义和校验部分,run() 与标定算法需要结合库的实际情况实现。

from pathlib import Path

import cv2


class MylibError(Exception):
    pass


class VideoProcessingError(MylibError):
    pass


class CalibrationError(MylibError):
    pass


class Detector:
    def _validate_video(self, video_path):
        path = Path(video_path)
        if not path.exists():
            raise FileNotFoundError(f"Video not found: {path}")

        cap = None
        try:
            cap = cv2.VideoCapture(str(path))
            if not cap.isOpened():
                raise VideoProcessingError(
                    f"Cannot open video: {path}"
                )

            ret, frame = cap.read()
            if not ret or frame is None:
                raise VideoProcessingError(
                    f"Cannot read first frame: {path}"
                )
        except cv2.error as exc:
            raise VideoProcessingError(
                f"Video backend failed: {path}"
            ) from exc
        finally:
            if cap is not None:
                cap.release()

使用时需要注意以下边界:

  • exists() 只是预检查。检查后文件仍可能被删除或改变,后续打开也可能失败。
  • isOpened() 返回失败,不能确定文件已损坏或格式错误。错误消息应描述观察到的失败,避免猜测具体原因。
  • 首帧读取成功只能说明取得了首帧,不能证明整个视频都能完整读取。
  • 持续读取时,read() 返回 False 也可能表示视频正常结束。应根据是否必须取得这一帧的业务约定,决定是否抛出异常。OpenCV VideoCapture 文档

标定阶段确认检测结果不足时,可以直接抛出 CalibrationError,并在消息中说明实际缺少什么条件。由于输入没有提供标定实现,无法确定具体检测阈值或判定公式。

调用方必须导入所有异常吗

不需要。调用方可以只导入库,通过模块名访问异常:

import mylib


detector = mylib.Detector()

try:
    detector.run("video.mp4")
except FileNotFoundError as exc:
    print(f"找不到视频文件:{exc}")
except mylib.CalibrationError as exc:
    print(f"标定失败:{exc}")
except mylib.VideoProcessingError as exc:
    print(f"视频处理失败:{exc}")

这种写法假设库在公开入口导出了这些类。也可以将异常统一放在 mylib.errors 中,让调用方使用 mylib.errors.CalibrationError。

具体异常应放在公共父类之前捕获,否则父类分支会先匹配,后面的细分处理便不会执行。Python 异常处理教程

库需要提供稳定的公开导入路径,并说明每个异常何时抛出。调用方只需了解自己准备处理的类型。

requests、NumPy 和 pandas 怎么做

这三个库都提供自定义异常,调用方无需导入全部类型。

  • requests 提供 RequestException,以及 Timeout、ConnectionError 等子类。调用方可以导入某个类,也可以使用 requests.exceptions.Timeout 这样的限定名称。Requests 异常定义
  • NumPy 提供 numpy.exceptions.AxisError。文档明确说明,为了兼容历史上捕获 ValueError 或 IndexError 的代码,它同时继承了这两个类型。这是针对兼容需求的具体设计,新库不必因此普遍采用异常多继承。NumPy AxisError 文档
  • pandas 提供 pandas.errors.EmptyDataError 等异常。例如,read_csv 遇到没有数据或表头的输入时可能抛出该异常。调用方可以通过 pd.errors.EmptyDataError 引用它。pandas EmptyDataError 文档

自定义异常是常见做法,但各个库不必采用同一套异常层级。

为什么不应该统一改抛 Exception

原先的尝试在捕获 VideoProcessingError 后执行:

raise Exception("Video validation failed.")

这样会改变调用方直接捕获到的异常类型,也会使外层消息失去具体细节。不过,“所有错误信息都丢失”并不准确:在 except 内抛出新异常时,Python 默认会保留原异常作为隐式上下文。

如果无需转换异常,让它直接传播即可。确实需要记录日志时,可以记录后使用裸 raise:

except VideoProcessingError:
    logger.exception("Video validation failed")
    raise

如果需要把底层异常转换成公开的领域异常,则使用显式异常链:

except cv2.error as exc:
    raise VideoProcessingError("Video decoding failed") from exc

raise ... from exc 会保留明确的因果关系;裸 raise 则重新抛出当前异常。Python 异常链教程

建议只在能够恢复、增加必要上下文或转换抽象层次的位置捕获异常。避免把整个 run() 中的任意 Exception 都转换成视频错误,否则程序缺陷也可能被误报为输入问题。日志由库或应用的合适一层负责,以减少重复记录。

公开文档应列出预期异常、触发条件和可供读取的属性。调用方应按异常类型或结构化属性判断,避免解析错误消息来决定处理流程。

备注:内容仅供参考。