exception - 现代Python适当的方式来声明自定义异常?

  显示原文与译文双语对照的内容

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

"现代 python"我指的是在 python 2.5中运行的东西,但是 python 2.6和 python 3的'正确'。* 做事方式。 "自定义"我指的是一个异常对象,它可以包含关于错误原因的额外数据: 字符串,也可能是其他与异常相关的任意对象。

我被 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中放弃争论。

更新:有两个回答建议重写 __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__() 或者。 如果你想要在引发异常时提供信息性消息,请执行以下操作:


class MyException(Exception):
 pass

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

这将给出一个以结尾结尾的回溯 MyException: My hovercraft is full of eels

如果你想从例外中获得更多的flexibiilty,你可以通过字典作为参数:


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

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

你可能会做得更好,但这没关系,除非你的异常确实是一个更具体的异常类型:


class MyException(Exception):
 pass

如果你的异常是一个更特定的异常类型,那么例外的子类,而不是一般的Exception 。 此外,你至少可以提供一个文档字符串( 并且不被强制使用 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(MyValueError, self).__init__(message, foo, *args) 

确实不需要编写你自己的__str__ 或者 __repr__ 。 内置的cooperative非常好,你的协作继承确保你使用它。


批判的最佳方案

我可能漏掉了问题,但为什么不:


class MyException(Exception):
 pass

同样,上面的问题是,为了捕获它,你必须指定( 如果在别处创建,则导入它) 或者捕获异常,( 但是你可能没有准备好处理所有类型的异常,并且你应该只捕获准备处理的异常) 。 对下面的类似批评,但这不是通过 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

这样你能通过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'

看看默认异常工作如果 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')

因此,你可能想要有一个"英镑异常模板",作为一个例外,以兼容的方式运行:


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

如果你不喜欢默认的tuple-like表示,只需将 __str__ 方法添加到 ExceptionTemplate 类,就像:


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

你将拥有


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

你应该重写 __repr__ 或者 __unicode__ 方法而不是使用消息,构造异常时提供的参数将在你需要的args 成员变量中出现。

不,"邮件"不被禁止。 它只是不推荐使用。你的应用程序可以使用消息。 但是,你可能想要去掉过时的错误,当然。

当你为你的应用程序创建自定义异常类,他们中的许多人不从子类异常,但从其他人,像valueerror或类似。 那么你必须适应他们对变量的使用。

如果你有很多例外在你的应用程序通常是一个好主意有共同定义的基类,这样用户可以做你的模块


try:
. . .
except NelsonsExceptions:
. . .

在这种情况下,你可以在那里执行所需的__init__ and __str__,这样你就不必每次都重复它。 但是只调用消息变量而不是消息就可以了。

在任何情况下,如果你做的事情不同于异常本身,那么你只需要 __init__ or __str__ 。 而且,如果不推荐使用,那么你需要两个,或者得到一个错误。 这并不是你每类需要的额外代码。 ; )

...