协慌网

登录 贡献 社区

在现代 Python 中声明自定义异常的正确方法?

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

通过 “现代 Python”,我指的是将在 Python 2.5 中运行的东西,但对于 Python 2.6 和 Python 3 * 的处理方式来说是 “正确的”。而 “自定义” 我指的是一个 Exception 对象,它可以包含有关错误原因的额外数据:一个字符串,也许还有一些与异常相关的任意对象。

我被 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有一些神奇的参数args ,但我从来不知道如何使用它。我也不确定这是向前发展的正确方法; 我在网上发现的很多讨论都表明他们试图在 Python 3 中废除 args。

更新:建议覆盖__init____str__ / __unicode__ / __repr__两个答案。这似乎很多打字,是否有必要?

答案

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

class MyException(Exception):
    pass

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

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获取它


Python 3 更新:在 Python 3 + 中,您可以使用super()稍微更紧凑的使用:

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

使用现代 Python 异常,您不需要滥用.message ,或覆盖.__str__().__repr__()或其中任何一个。如果你想要的只是提出异常的信息,请执行以下操作:

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

这将以MyException: My hovercraft is full of eels结束追溯MyException: My hovercraft is full of eels

如果您希望从异常中获得更大的灵活性,可以将字典作为参数传递:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

但是,在except块中获取这些细节有点复杂。详细信息存储在args属性中,该属性是一个列表。你需要做这样的事情:

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

仍然可以将多个项目传递给异常并通过元组索引访问它们,但这是非常不鼓励的 (甚至打算暂时弃用)。如果您确实需要多条信息并且上述方法对您来说还不够,那么您应该按照本教程中的描述继承Exception

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

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

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

class MyException(Exception):
    pass

或者更好(也许是完美的),而不是pass给文档字符串:

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

子类化异常子类

来自文档

Exception

所有内置的,非系统退出的异常都派生自此类。所有用户定义的异常也应该从该类派生。

这意味着如果您的异常是一种更具体的异常,则将该异常子类化为通用Exception (并且结果将是您仍然从文档推荐的Exception派生的)。此外,您至少可以提供 docstring(而不是强制使用pass关键字):

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

使用自定义__init__设置您自己创建的属性。避免传递 dict 作为位置参数,未来的代码用户会感谢你。如果您使用已弃用的消息属性,则自行分配将避免DeprecationWarning

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初始化的方式,如果你访问消息属性,你将得到一个DeprecationWarning

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

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之外)。不多也不少。这是未来用户可能不会欣赏的有趣约束。

直接 - 它违反了Liskov 的可替代性

我将演示这两个错误:

>>> 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'