在现代 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__
。这似乎是很多打字,有必要吗?
There should be one-- and preferably only one --obvious way to do it.
也许我错过了这个问题,但为什么不呢:
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
这样,您可以将错误消息的 dict 传递给第二个参数,稍后使用 e.errors
获取它。
在 Python 2 中,您必须使用这种稍微复杂的 super()
形式:
super(ValidationError, self).__init__(message)
使用现代 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
Exception(foo, bar, qux)
。
“在现代 Python 中声明自定义异常的正确方法是什么?”
这很好,除非您的异常确实是一种更具体的异常:
class MyException(Exception):
pass
或者更好(也许是完美的),而不是 pass
给出一个文档字符串:
class MyException(Exception):
"""Raise for my specific kind of exception"""
子类化异常子类
从docs
异常 所有内置的、非系统退出的异常都派生自这个类。所有用户定义的异常也应该从这个类派生。
这意味着如果您的异常是一种更具体的异常,则将该异常子类化而不是通用 Exception
(结果将是您仍然按照文档建议从 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(MyAppValueError, self).__init__(message, foo, *args)
确实没有必要编写自己的 __str__
或 __repr__
。内置的非常好,您的合作继承确保您使用它们。
对最佳答案的批评
也许我错过了这个问题,但为什么不呢:
class MyException(Exception):
pass
同样,上面的问题是,为了捕捉它,你要么必须专门命名它(如果在别处创建,则导入它)或捕捉异常,(但你可能不准备处理所有类型的异常,并且您应该只捕获您准备处理的异常)。与以下类似的批评,但另外这不是通过 super
初始化的方式,如果您访问 message 属性,您将获得 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
)。不多也不少。这是一个有趣的约束,未来的用户可能不会喜欢。
直截了当 - 它违反了 Liskov substitutability。
我将演示这两个错误:
>>> 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'
BaseException.message
在 Python 3 中消失了,所以批评只适用于旧版本,对吧?
ValueError
的附加选项。如果它属于值错误类别,这是有道理的。如果它不在值错误的类别中,我会在语义上反对它。程序员有一些细微差别和推理的空间,但我更喜欢适用时的特异性。我会更新我的答案,以便在不久的将来更好地解决这个问题。
如果使用一个或多个属性(省略回溯),请查看默认情况下异常如何工作:
>>> 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
截至 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
语句来寻找解决方案。这对我来说似乎更清晰/更蟒蛇。 Java 中大量使用高级异常,当使用框架并且必须捕获所有可能的错误时,这有时会很烦人。
__str__
)而不是使用 super().__init__(...)
的其他答案。只是覆盖 __str__
和 __repr__
的耻辱可能只是为了更好的“默认”序列化。
要正确定义您自己的异常,您应该遵循一些最佳实践:
定义一个继承自 Exception 的基类。这将允许轻松捕获与项目相关的任何异常: class MyProjectError(Exception): """A base class for MyProject exceptions.""" 在单独的模块中组织异常类(例如 exceptions.py)通常是一个好方法主意。
要创建特定异常,请对基异常类进行子类化。 class CustomError(MyProjectError): """MyProject 的自定义异常类。""" 您也可以继承自定义异常类以创建层次结构。
要为自定义异常添加对额外参数的支持,请定义具有可变数量参数的 __init__() 方法。调用基类的 __init__(),将任何位置参数传递给它(记住 BaseException/Exception 需要任意数量的位置参数)。为实例存储额外的参数,例如: class CustomError(MyProjectError): def __init__(self, *args, **kwargs): super().__init__(*args) self.foo = kwargs.get('foo ') 要使用额外的参数引发此类异常,您可以使用: raise CustomError('Something bad occurred', foo='foo')
此设计遵循 Liskov substitution principle,因为您可以用派生异常类的实例替换基本异常类的实例。此外,它还允许您创建具有与父类相同参数的派生类的实例。
您应该覆盖 __repr__
或 __unicode__
方法而不是使用消息,您在构造异常时提供的参数将在异常对象的 args
属性中。
请参阅一篇非常好的文章“The definitive guide to Python exceptions”。基本原则是:
总是从(至少)异常继承。
始终只使用一个参数调用 BaseException.__init__。
在构建库时,定义一个继承自 Exception 的基类。
提供有关错误的详细信息。
在有意义时从内置异常类型继承。
还有关于组织(在模块中)和包装异常的信息,我建议阅读指南。
Always call BaseException.__init__ with only one argument.
似乎不需要约束,因为它实际上接受任意数量 个参数。
不,“消息”是不被禁止的。它只是被弃用了。您的应用程序将使用消息正常工作。当然,您可能希望摆脱弃用错误。
当您为应用程序创建自定义 Exception 类时,它们中的许多不仅是 Exception 的子类,而是来自其他的子类,例如 ValueError
或类似的。然后你必须适应他们对变量的使用。
而且,如果您的应用程序中有许多例外情况,通常最好为所有例外情况提供一个通用的自定义基类,以便您的模块的用户可以这样做
try:
...
except NelsonsExceptions:
...
在这种情况下,您可以在此处执行所需的 __init__
和 __str__
,因此您不必为每个异常都重复它。但是简单地调用消息变量而不是消息就可以了。
在任何情况下,如果您执行的操作与 Exception 本身不同,您只需要 __init__
或 __str__
。并且因为如果弃用,那么你需要两者,否则你会得到一个错误。这并不是每个班级所需的大量额外代码。
试试这个例子
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")
一个非常简单的方法:
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)
为了最大限度地进行自定义,要定义自定义错误,您可能需要定义一个继承自 Exception
类的中间类:
class BaseCustomException(Exception):
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return self.msg
class MyCustomError(BaseCustomException):
"""raise my custom error"""
我遇到了这个线程。这就是我做自定义异常的方式。虽然 Fault
类稍微复杂一些,但它使得使用可变参数声明自定义表达异常变得微不足道。
FinalViolation
、SingletonViolation
都是 TypeError
的子类,因此将在下面捕获代码。
try:
<do something>
except TypeError as ex:
<handler>
这就是 Fault
不继承自 Exception
的原因。允许派生异常从他们选择的异常中继承。
class Fault:
"""Generic Exception base class. Note not descendant of Exception
Inheriting 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."
FinalViolation
、SingletonViolation
很遗憾只接受 1 个参数。
但是可以很容易地创建一个多参数错误,例如
class VesselLoadingError(Fault, BufferError):
formats = "My {} is full of {}."
raise VesselLoadingError('hovercraft', 'eels')
__main__.VesselLoadingError:我的气垫船装满了鳗鱼。
对我来说,它只是 __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 = Message
return
def __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 main
result = read_input("1", "2", Newpassword, "4")
File "ASCII.py", line 69, in read_input
raise localbreak(Error_codes[102]) At least 18 alphanumeric characters and list of special chars !@#$&*
__main__.localbreak: False