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

时间:2009-08-23 21:29:30

标签: python exception custom-exceptions

在现代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__两个答案。这似乎是很多打字,是否有必要?

11 个答案:

答案 0 :(得分:1120)

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

class MyException(Exception):
    pass

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

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

答案 1 :(得分:427)

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

class MyException(Exception):
    pass

raise 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"])

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

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

答案 2 :(得分:172)

  

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

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

class MyException(Exception):
    pass

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

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

子类化异常子类

来自docs

  

Exception

     

所有内置的,非系统退出的异常都是从这个类派生的。   所有用户定义的异常也应该从中派生出来   类。

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

pass

使用自定义class MyAppValueError(ValueError): '''Raise when my specific value is wrong''' 设置您自己创建的属性。避免传递dict作为位置参数,未来的代码用户会感谢你。如果您使用已弃用的邮件属性,则自行分配将避免使用__init__

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
  

这样你就可以将错误消息的dict传递给第二个参数,稍后通过e.errors

进入它

它还需要传递两个参数(除了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 之外)。不多也不少。这是未来用户可能不会欣赏的有趣约束。

要直接 - 它违反了Liskov substitutability

我将证明这两个错误:

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'

答案 3 :(得分:44)

如果使用了一个 vs 更多属性(省略了回溯),请查看默认情况下异常如何工作:

>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

所以你可能想要一种“例外模板”,以兼容的方式作为例外工作:

>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened

>>> raise nastyerr()
NastyError: bad thing happened

>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')

这可以通过这个子类轻松完成

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

如果您不喜欢这种默认的类似于元组的表示,只需将__str__方法添加到ExceptionTemplate类,例如:

    # ...
    def __str__(self):
        return ': '.join(self.args)

你将拥有

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken

答案 4 :(得分:15)

您应该覆盖__repr____unicode__方法而不是使用消息,构造异常时提供的参数将位于异常对象的args属性中。

答案 5 :(得分:14)

要正确定义自己的异常,应遵循一些最佳做法:

  • 定义从Exception继承的基类。这样可以轻松捕获与项目相关的任何异常:

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""
    

    在单独的模块(例如exceptions.py)中组织异常类通常是个好主意。

  • 要创建特定的异常,请对基本异常类进行子类化。

  • 要在自定义异常中添加对额外自变量的支持,请定义自变量__init__()的自定义方法。调用基类的__init__(),将任何位置参数传递给基类(请记住,BaseException/Exception期望有任意数量的位置参数):

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super().__init__(*args)
            self.foo = kwargs.get('foo')
    

    要使用额外的参数引发此类异常,可以使用:

     raise CustomError('Something bad happened', foo='foo')
    

此设计遵循Liskov substitution principle,因为您可以用派生异常类的实例替换基本异常类的实例。此外,它还允许您使用与父级相同的参数来创建派生类的实例。

答案 6 :(得分:6)

不,“消息”不被禁止。它刚刚被弃用了。您的应用程序将使用消息正常工作。但是你可能想要摆脱弃用错误。

当您为应用程序创建自定义Exception类时,其中许多不仅仅是从Exception继承,而是从其他类(如ValueError或类似的)继承。然后你必须适应他们对变量的使用。

如果你的应用程序中有很多例外,那么为所有这些例子设置一个通用的自定义基类通常是一个好主意,这样你的模块用户就可以了

try:
    ...
except NelsonsExceptions:
    ...

在这种情况下,您可以执行那里所需的__init__ and __str__,因此您不必为每个例外重复它。但是简单地将消息变量调用为消息之外的其他内容就可以解决问题了。

在任何情况下,如果您执行与Exception本身不同的操作,则只需要__init__ or __str__。因为如果弃用,则需要两者,否则会出错。这不是每个课程所需的额外代码。 ;)

答案 7 :(得分:6)

自python 3.8起(2018年,https://docs.python.org/dev/whatsnew/3.8.html),推荐的方法仍然是:

class CustomExceptionName(Exception):
    """Exception raised when very uncommon things happen"""
    pass

请不要忘记记录文档,为什么需要自定义例外!

如果需要,这是处理包含更多数据的异常的方法:

class CustomExceptionName(Exception):
    """Still an exception raised when uncommon things happen"""
    def __init__(self, message, payload=None):
        self.message = message
        self.payload = payload # you could add more args
    def __str__(self):
        return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types

并像这样获取它们:

try:
    raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
    print(str(error)) # Very bad mistake
    print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1

payload=None对于使其可腌制很重要。转储之前,您必须调用error.__reduce__()。加载将按预期工作。

如果需要将大量数据传输到某些外部结构,则可能应该调查使用pythons return语句找到解决方案。对我来说,这似乎更清楚/更pythonic。 Java中大量使用了高级异常,当使用框架并不得不捕获所有可能的错误时,有时会很烦人。

答案 8 :(得分:3)

一种非常简单的方法:

class CustomError(Exception):
    pass

raise CustomError("Hmm, seems like this was custom coded...")

或者,在不打印__main__的情况下引发错误(看起来更整洁):

class CustomError(Exception):
    __module__ = Exception.__module__

raise CustomError("Improved CustomError!")

答案 9 :(得分:1)

尝试这个例子

class InvalidInputError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return repr(self.msg)

inp = int(input("Enter a number between 1 to 10:"))
try:
    if type(inp) != int or inp not in list(range(1,11)):
        raise InvalidInputError
except InvalidInputError:
    print("Invalid input entered")

答案 10 :(得分:1)

看到一篇很好的文章“ The definitive guide to Python exceptions”。基本原则是:

  • 始终(至少)从Exception继承。
  • 始终仅用一个参数调用BaseException.__init__
  • 构建库时,请定义从Exception继承的基类。
  • 提供有关错误的详细信息。
  • 从内置异常类型继承是合理的。

还有关于组织(在模块中)和包装异常的信息,我建议阅读指南。