在现代Python中声明自定义异常类的正确方法是什么?我的主要目标是遵循其他异常类所具有的任何标准,以便(例如)我在异常中包含的任何额外字符串都由捕捉到异常的任何工具打印出来。

我所说的“现代Python”是指在Python2.5中运行,但对于Python2.6和Python3.*的工作方式来说是“正确的”。我所说的“自定义”是指一个异常对象,它可以包含有关错误原因的额外数据:一个字符串,也可能是与异常相关的其他任意对象。

我被Python 2.6.2中的以下弃用警告绊倒了:

>>> class MyError(Exception):
...     def __init__(self, message):
...         self.message = message
... 
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

BaseException对名为message的属性具有特殊含义,这似乎很疯狂。我从PEP-352中了解到,这个属性在2.5中确实有特殊的含义,他们正试图贬低它,所以我想这个名字(以及这个名字)现在被禁止了?呃。

我还模糊地意识到Exception有一些神奇的参数参数,但我从未知道如何使用它;我在网上发现的许多讨论都表明,他们试图在Python 3中取消args。

更新:两个答案建议重写__init__和__str__/__unicode__/__repr_。这似乎需要很多打字,是吗?


当前回答

也许我错过了这个问题,但为什么不呢

class MyException(Exception):
    pass

要覆盖某些内容(或传递额外的参数),请执行以下操作:

class ValidationError(Exception):
    def __init__(self, message, errors):            
        # Call the base class constructor with the parameters it needs
        super().__init__(message)
            
        # Now for your custom code...
        self.errors = errors

这样,您可以将错误消息的dict传递给第二个参数,然后使用e.errors进行处理。

在Python 2中,您必须使用稍微复杂一点的super()形式:

super(ValidationError, self).__init__(message)

其他回答

“在现代Python中声明自定义异常的正确方法是什么?”

这很好,除非您的异常确实是一种更具体的异常:

class MyException(Exception):
    pass

或者更好(也许完美),而不是传递一个docstring:

class MyException(Exception):
    """Raise for my specific kind of exception"""

子类化异常子类

从文档中

例外所有内置的、非系统退出的异常都是从该类派生的。所有用户定义的异常也应由此派生班

这意味着,如果您的异常是一种更具体的异常类型,请将该异常子类化,而不是泛型异常(结果将是您仍然按照文档的建议从异常派生)。此外,您至少可以提供一个docstring(不必强制使用pass关键字):

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

使用自定义__init__设置您自己创建的属性。避免将dict作为位置参数传递,代码的未来用户将感谢您。如果使用不推荐的消息属性,则自行分配该属性将避免出现不推荐的警告:

class MyAppValueError(ValueError):
    '''Raise when a specific subset of values in context of app is wrong'''
    def __init__(self, message, foo, *args):
        self.message = message # without this you may get DeprecationWarning
        # Special attribute you desire with your Error, 
        # perhaps the value that caused the error?:
        self.foo = foo         
        # allow users initialize misc. arguments as any other builtin Error
        super(MyAppValueError, self).__init__(message, foo, *args) 

真的没有必要编写自己的__str__或__repr_。内置的非常好,您的合作继承确保您使用它们。

对顶级答案的批评

也许我错过了这个问题,但为什么不呢

class MyException(Exception):
    pass

同样,上面提到的问题是,为了捕获异常,您必须对其进行专门命名(如果在其他地方创建,则将其导入)或捕获异常(但您可能没有准备好处理所有类型的异常,您应该只捕获准备好处理的异常)。与下面的批评类似,但另外,这不是通过super进行初始化的方法,如果访问message属性,您将收到DeprecationWarning:

编辑:要覆盖某些内容(或传递额外的参数),请执行以下操作:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

这样,您就可以将错误消息的dict传递给第二个参数,然后使用e.errors进行处理

它还需要正好传入两个参数(除了self)。这是一个有趣的限制,未来的用户可能不会理解。

直接地说,这违反了利斯科夫的可替代性。

我将演示这两个错误:

>>> ValidationError('foo', 'bar', 'baz').message

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)

>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'

对比:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'

我偶然发现了这条线索。这就是我如何处理自定义异常。虽然Fault类有点复杂,但它使用变量参数声明自定义表达异常变得微不足道。

FinalVibration和SingletonVibration都是TypeError的子类,因此将在下面捕获代码。

try:
    <do something>
except TypeError as ex:
    <handler>

这就是Fault不能继承自Exception的原因。允许派生异常从其选择的异常继承。

class Fault:
    """Generic Exception base class. Note not descendant of Exception
Inheriting exceptions override formats"""
    formats = '' # to be overriden in descendant classes

    def __init__(self, *args):
        """Just save args for __str__"""
        self.args = args

    def __str__(self):
        """Use formats declared in descendant classes, and saved args to build exception text"""
        return self.formats.format(*self.args)

class TypeFault(Fault, TypeError):
    """Helper class mixing Fault and TypeError"""

class FinalViolation(TypeFault):
    """Custom exception raised if inheriting from 'final' class"""
    formats = "type {} is not an acceptable base type. It cannot be inherited from."

class SingletonViolation(TypeFault):     
    """Custom exception raised if instancing 'singleton' class a second time"""
    formats = "type {} is a singleton. It can only be instanced once."

FinalViolation,SingletonViolation不幸只接受1个参数。

但是可以很容易地创建多参数错误,例如。

class VesselLoadingError(Fault, BufferError):
    formats = "My {} is full of {}."

raise VesselLoadingError('hovercraft', 'eels')

__main__.船只装载错误:我的气垫船上满是鳗鱼。

您应该重写__repr_或__unicode__方法,而不是使用message,构造异常时提供的参数将位于异常对象的args属性中。

也许我错过了这个问题,但为什么不呢

class MyException(Exception):
    pass

要覆盖某些内容(或传递额外的参数),请执行以下操作:

class ValidationError(Exception):
    def __init__(self, message, errors):            
        # Call the base class constructor with the parameters it needs
        super().__init__(message)
            
        # Now for your custom code...
        self.errors = errors

这样,您可以将错误消息的dict传递给第二个参数,然后使用e.errors进行处理。

在Python 2中,您必须使用稍微复杂一点的super()形式:

super(ValidationError, self).__init__(message)

对我来说,这只是__init__和变量,但有时会进行测试。

我的示例:

Error_codes = { 100: "Not enough parameters", 101: "Number of special characters more than limits", 102: "At least 18 alphanumeric characters and list of special chars !@#$&*" }

class localbreak( Exception ) :
    Message = ""
    
    def __init__(self, Message):
        self.Message = Message
        return
    def __str__(self):
        print(self.Message)
        return "False"

### When calling ...
raise localbreak(Error_codes[102])

输出:

Traceback (most recent call last):   File "ASCII.py", line 150, in <module>
    main(OldPassword, Newpassword)   File "ASCII.py", line 39, in main
    result = read_input("1", "2", Newpassword, "4")                                     
    File "ASCII.py", line 69, in read_input
    raise localbreak(Error_codes[102]) At least 18 alphanumeric characters and list of special chars !@#$&*
__main__.localbreak: False