提问人:Nelson 提问时间:8/24/2009 最后编辑:SultanOrazbayevNelson 更新时间:9/11/2023 访问量:1086488
在现代 Python 中声明自定义异常的正确方法?
Proper way to declare custom exceptions in modern Python?
问:
在现代 Python 中声明自定义异常类的正确方法是什么?我的主要目标是遵循其他异常类所具有的任何标准,以便(例如)我在异常中包含的任何额外字符串都会被捕获异常的任何工具打印出来。
我所说的“现代 Python”是指将在 Python 2.5 中运行的东西,但对于 Python 2.6 和 Python 3.* 的做事方式来说是“正确的”。我所说的“custom”是指一个对象,它可以包含有关错误原因的额外数据:一个字符串,也可能是与异常相关的其他一些任意对象。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
这似乎很疯狂,对名为 的属性具有特殊含义。我从 PEP-352 中收集到该属性在他们试图弃用的 2.5 中确实具有特殊含义,所以我想这个名字(而且只有那个)现在被禁止了?呸。BaseException
message
我也模糊地意识到它有一些神奇的参数,但我从来不知道如何使用它。我也不确定这是未来做事的正确方式;我在网上找到的很多讨论都表明他们正试图在 Python 3 中取消 args。Exception
args
更新:两个答案建议覆盖 ,和 //。这似乎是很多打字,有必要吗?__init__
__str__
__unicode__
__repr__
答:
您应该重写 or 方法而不是使用 message,您在构造异常时提供的参数将位于异常对象的属性中。__repr__
__unicode__
args
也许我错过了这个问题,但为什么不呢:
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)
评论
不,“消息”不是被禁止的。它只是被弃用了。您的应用程序将很好地使用消息。但是,当然,您可能希望摆脱弃用错误。
当您为应用程序创建自定义 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 = message
self.animal = animal
def __str__(self):
return self.message
评论
Exception(foo, bar, qux)
查看默认情况下,如果使用一个或多个属性(省略回溯),异常是如何工作的:
>>> 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 中声明自定义异常的正确方法是什么?”
这很好,除非您的异常确实是一种更具体的异常:
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'''
设置您使用自定义 .避免将 dict 作为位置参数传递,您的代码的未来用户会感谢您。如果您使用已弃用的 message 属性,则自行分配该属性将避免:__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'
评论
BaseException.message
ValueError
试试这个例子
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")
从 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 语句寻找解决方案。这对我来说似乎更清晰/更蟒蛇。高级异常在 Java 中被大量使用,当使用框架并且必须捕获所有可能的错误时,这有时会很烦人。return
评论
__str__
super().__init__(...)
__str__
__repr__
请参阅一篇非常好的文章“Python 异常的权威指南”。基本原则是:
- 始终继承自(至少)异常。
- 始终只使用一个参数进行调用。
BaseException.__init__
- 生成库时,定义继承自 Exception 的基类。
- 提供有关错误的详细信息。
- 在有意义时从内置异常类型继承。
还有关于组织(在模块中)和包装异常的信息,我建议阅读指南。
评论
Always call BaseException.__init__ with only one argument.
似乎是不需要的约束,因为它实际上接受任意数量的参数。
若要正确定义自己的例外,应遵循以下一些最佳做法:
定义继承自 的基类。这将允许轻松捕获与项目相关的任何异常:
Exception
class MyProjectError(Exception): """A base class for MyProject exceptions."""
将异常类组织在一个单独的模块中(例如)通常是一个好主意。
exceptions.py
若要创建特定异常,请对基异常类进行子类化。
class CustomError(MyProjectError): """A custom exception class for MyProject."""
您也可以对自定义异常类进行子类化以创建层次结构。
若要向自定义异常添加对额外参数的支持,请定义具有可变数量的参数的方法。调用基类的 ,将任何位置参数传递给它(请记住,
BaseException
/Exception
需要任意数量的位置参数)。将额外的关键字参数存储到实例中,例如:__init__()
__init__()
class CustomError(MyProjectError): def __init__(self, *args, **kwargs): super().__init__(*args) self.custom_kwarg = kwargs.get('custom_kwarg')
使用示例:
try: raise CustomError('Something bad happened', custom_kwarg='value') except CustomError as exc: print(f'Сaught CustomError exception with custom_kwarg={exc.custom_kwarg}')
此设计遵循 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
FinalViolation
,都是 so 的子类,因此将在下面捕获代码。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
,不幸的是只接受 1 个参数。SingletonViolation
但是可以很容易地创建一个多参数错误,例如
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 = 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
为了最大程度地自定义,要定义自定义错误,您可能需要定义一个从类继承的中间类,如下所示:Exception
class BaseCustomException(Exception):
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return self.msg
class MyCustomError(BaseCustomException):
"""raise my custom error"""
可以使用它来简化自定义异常的定义:dataclass
from dataclasses import dataclass
@dataclass
class MyException(Exception):
message: str = "This is a custom exception"
def __str__(self):
return f"Custom message: {self.message.upper()}"
raise MyException("abcdef")
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# MyException: Custom message: ABCDEF
raise MyException()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# MyException: Custom message: THIS IS A CUSTOM EXCEPTION
这减少了一些样板,同时保持了进一步定制的灵活性。
评论
There should be one-- and preferably only one --obvious way to do it.