现代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__
。这看起来需要写很多代码,这真的有必要吗?
17 个回答
“在现代Python中,声明自定义异常的正确方法是什么?”
这样做是可以的,前提是你的异常确实是某种更具体异常的类型:
class MyException(Exception):
pass
或者更好(也许是完美的),与其用pass
,不如给它加个文档字符串:
class MyException(Exception):
"""Raise for my specific kind of exception"""
继承异常子类
来自文档
Exception
所有内置的、非系统退出的异常都是从这个类派生的。所有用户定义的异常也应该从这个类派生。
这意味着如果你的异常是某种更具体异常的类型,应该继承那个具体的异常,而不是通用的Exception
(这样做的结果是你仍然是从Exception
派生的,正如文档所推荐的)。另外,你至少可以提供一个文档字符串(而不必强制使用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 Error
super(MyAppValueError, self).__init__(message, foo, *args)
其实没必要自己写__str__
或__repr__
。内置的这些方法已经很好了,而你的协作继承确保你会使用它们。
对最佳答案的批评
也许我没理解问题,但为什么不这样做:
class MyException(Exception):
pass
上面的问题在于,要捕获它,你要么得具体命名它(如果在其他地方创建的话,还得导入),要么捕获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
)。不能多也不能少。这是一个有趣的限制,未来的用户可能不太喜欢。
直接说,这违反了里斯科夫替换原则。
我将演示这两个错误:
>>> 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'
在现代的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 = message
self.animal = animal
def __str__(self):
return self.message
也许我没理解问题,但为什么不这样做呢:
class MyException(Exception):
pass
如果你想要覆盖某些内容(或者传递额外的参数),可以这样做:
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
这样你就可以把错误信息的字典传给第二个参数,之后可以通过 e.errors
来获取这些信息。
在Python 2中,你需要使用这种稍微复杂一点的 super()
形式:
super(ValidationError, self).__init__(message)