在现代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__。这看起来像很多打字,有必要吗?

945645 次浏览

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

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

class MyException(Exception):pass

要覆盖某些内容(或传递额外的参数),请这样做:

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

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

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

super(ValidationError, self).__init__(message)

不,“消息”不是被禁止的。它只是被弃用了。您的应用程序可以很好地使用消息。但是您可能希望摆脱弃用错误,当然。

当您为您的应用程序创建自定义Exception类时,它们中的许多不仅来自Exception,还来自其他类,例如ValueError或类似的类。然后您必须适应他们对变量的使用。

如果您的应用程序中有许多异常,那么为所有这些异常提供一个通用的自定义基类通常是个好主意,以便您的模块的用户可以执行

try:...except NelsonsExceptions:...

在这种情况下,您可以在那里执行所需的__init____str__,因此您不必对每个异常重复它。但只需调用消息变量而不是消息即可。

无论如何,如果你做的与Exception本身不同,你只需要__init____str__。因为如果弃用,你就需要两者,否则你会得到错误。这不是每个类需要的大量额外代码。

使用现代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"])

仍然可以将多个项目传入异常并通过元组索引访问它们,但这是非常气馁(甚至不久前打算弃用)。如果你确实需要不止一条信息,并且上述方法对你来说还不够,那么你应该像教程中描述的那样子类Exception

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

如果使用了一个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 nastyerrNastyError: 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

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

class MyException(Exception):pass

或者更好(也许完美),而不是pass给出一个docstring:

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__自己创建的属性。避免将字典作为位置参数传递,您代码的未来用户会感谢您。如果您使用已弃用的消息属性,自己分配它将避免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 Errorsuper(MyAppValueError, self).__init__(message, foo, *args)

实际上没有必要编写自己的__str____repr__。内置的非常好,您的合作继承确保您使用它们。

顶答案的批判

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

class MyException(Exception):pass

同样,上面的问题是,为了捕获它,你要么必须特别命名它(如果在其他地方创建则导入它),要么捕获Exception,(但你可能没有准备好处理所有类型的Exceptions,你应该只捕获你准备好处理的异常)。与下面类似的批评,但另外这不是通过super初始化的方式,如果你访问消息属性,你会得到DeprecationWarning

编辑:要覆盖某些内容(或传递额外的参数),请这样做:

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

这样,您就可以将错误消息传递给第二个参数,然后使用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').messageTypeError: __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'

试试这个例子

class InvalidInputError(Exception):def __init__(self, msg):self.msg = msgdef __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 InvalidInputErrorexcept InvalidInputError:print("Invalid input entered")

从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 = messageself.payload = payload # you could add more argsdef __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 mistakeprint("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1

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

如果你需要将大量数据传输到某个外部结构,你也许应该使用pythonsreturn语句来寻找解决方案。这对我来说似乎更清晰/更pythonic。高级异常在Java中大量使用,当使用框架并必须捕获所有可能的错误时,这有时会很烦人。

看到一篇很好的文章“Python异常的权威指南”。基本原则是:

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

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

要正确定义自己的异常,您应该遵循一些最佳实践:

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

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

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

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

    class CustomError(MyProjectError):"""A custom exception class for MyProject."""

    您也可以对自定义异常类进行子类化以创建层次结构。

  • 要向自定义异常添加对额外参数的支持,请定义一个具有可变数量参数的__init__()方法。调用基类的__init__(),将任何位置参数传递给它(记住#2/#3期望任意数量的位置论证)。将额外参数存储到实例,例如:

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

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

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

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

一个非常简单的方法:

class CustomError(Exception):pass
raise CustomError("Hmm, seems like this was custom coded...")

或者,在没有打印__main__的情况下引发错误(可能看起来更干净整洁):

class CustomError(Exception):__module__ = Exception.__module__
raise CustomError("Improved CustomError!")

从Python 3.9.5开始,我对上述方法存在问题。但是,我发现这对我很有用:

class MyException(Exception):"""Port Exception"""

然后它可以在代码中使用,例如:

try:raise MyException('Message')
except MyException as err:print (err)

我遇到了这个线程。这就是我自定义异常的方式。虽然Fault类有点复杂,但它使得使用变量参数声明自定义表达式异常变得微不足道。

FinalViolationSingletonViolation都是TypeError的子类,因此将在下面的代码中捕获。

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

这就是为什么Fault不继承Exception。允许派生异常从他们选择的异常继承。

class Fault:"""Generic Exception base class. Note not descendant of ExceptionInheriting 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."

FinalViolationSingletonViolation只接受1个参数。

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

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

__main__.VesselLoadingError: My hovercraft is full of eels.

对我来说,它只是__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 = Messagereturndef __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 mainresult = read_input("1", "2", Newpassword, "4")File "ASCII.py", line 69, in read_inputraise localbreak(Error_codes[102]) At least 18 alphanumeric characters and list of special chars !@#$&*__main__.localbreak: False

为了最大限度地自定义,要定义自定义错误,您可能需要将从Exception class继承的中间类定义为:

class BaseCustomException(Exception):def __init__(self, msg):self.msg = msg
def __repr__(self):return self.msg

class MyCustomError(BaseCustomException):"""raise my custom error"""