在现代Python中声明自定义exception的正确方法是什么?

在现代Python中声明自定义exception类的正确方法是什么? 我的主要目标是遵循任何标准的其他exception类,以便(例如)我包含在exception中的任何额外string通过任何工具捕获exception而打印出来。

对于“现代Python”,我指的是在Python 2.5中运行的东西,但对于Python 2.6和Python 3。 通过“自定义”,我的意思是一个Exception对象,可以包含有关错误原因的额外数据:一个string,也许还有一些其他与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 ,但我从来不知道如何使用它。 我也不确定这是做事的正确方法。 我在网上发现的很多讨论都build议他们试图取消Python 3中的参数。

更新:两个答案build议覆盖__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(ValidationError, self).__init__(message) # Now for your custom code... self.errors = errors 

这样你就可以把错误消息的字典传递给第二个参数,然后用e.errors来得到它

使用现代Python例外,您不需要滥用.message ,或覆盖.__str__().__repr__()或其中任何一个。 如果所有你想要的是一个信息性的消息,当你的exception提出,做到这一点:

 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

如果你想从exception中获得更多的灵活性,你可以传递一个字典作为参数:

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

然而,要想在这个区域中看到这些细节则要复杂一些。 它们存储在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,但将来会被弃用。 如果您确实需要更多的信息,那么您应该考虑完全inheritanceException

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

这很好,除非你的exception是一个更具体的例外:

 class MyException(Exception): pass 

或者更好(也许完美),而不是pass给文档string:

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

子类exception子类

从文档

Exception

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

这意味着, 如果你的exception是一个更具体的exceptiontypes,子类的exception,而不是generics的Exception (和结果将仍然从Exception推导为文档)。 另外,你至less可以提供一个文档string(而不是强制使用pass关键字):

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

使用自定义__init__设置您自己创build的属性。 避免将字典作为位置parameter passing,未来的代码用户将会感谢您。 如果您使用弃用的消息属性,则自行分配它将避免“弃用警告”:

 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__ 。 内置的非常好,你的合作inheritance保证你使用它。

评论最高的答案

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

 class MyException(Exception): pass 

同样,上面的问题是,为了抓住它,你要么专门命名(如果在其他地方创build的话,要导入它)或者捕获exception(但是你可能不准备处理所有types的exception,你应该只捕捉你准备处理的exception)。 类似的批评,下面,但另外,这不是通过super初始化的方式,如果您访问消息属性,你会得到一个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 

这样你就可以把错误消息的字典传递给第二个参数,然后用e.errors来得到它

它也需要两个参数(除了self )通过。没有更多,不能less。 这是一个有趣的限制,未来的用户可能不会欣赏。

直接 – 违反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' 

看看如何使用一个vs更多的属性默认情况下是如何工作的(tracebacks省略):

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

所以你可能想要有一个“ exception模板 ”,作为一个例外本身,以兼容的方式:

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

如果你不喜欢这种默认的元组表示forms,只需要在ExceptionTemplate类中添加__str__方法,如:

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

你会有的

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

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

不,“消息”不被禁止。 它只是被弃用。 您的应用程序将使用消息正常工作。 但是,当然,您可能希望摆脱弃用错误。

为应用程序创build自定义的Exception类时,其中的许多类不仅仅来自Exception,而且来自其他类,如ValueError或类似的。 那么你必须适应variables的使用。

而且,如果你的应用程序中有很多例外情况,那么为所有这些应用程序提供一个通用的自定义基类通常是个好主意,这样你的模块的用户就可以做

 try: ... except NelsonsExceptions: ... 

在这种情况下,您可以在那里__init__ and __str__所需的__init__ and __str__ ,所以您不必为每个exception都重复一遍。 但是,简单地调用消息variables除了消息之外就是一个窍门。

在任何情况下,如果您执行与Exception本身不同的操作,则只需要__init__ or __str__ 。 而且,因为如果弃用,那么您同时需要这两个,否则您会得到一个错误。 这不是每个class级所需的额外代码。 ;)