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

我所说的“现代Python”是指在Python2.5中运行,但对于Python2.6和Python3.*的工作方式来说是“正确的”。我所说的“自定义”是指一个异常对象,它可以包含有关错误原因的额外数据:一个字符串,也可能是与异常相关的其他任意对象。

我被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有一些神奇的参数参数,但我从未知道如何使用它;我在网上发现的许多讨论都表明,他们试图在Python 3中取消args。

更新:两个答案建议重写__init__和__str__/__unicode__/__repr_。这似乎需要很多打字,是吗?


当前回答

查看如果使用一个或多个属性(省略了回溯),默认情况下异常是如何工作的:

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

其他回答

为了实现最大程度的自定义,为了定义自定义错误,您可能需要定义一个继承自Exception类的中间类,如下所示:

class BaseCustomException(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __repr__(self):
        return self.msg


class MyCustomError(BaseCustomException):
    """raise my custom error"""

对我来说,这只是__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

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

有效负载=无对使其可酸洗很重要。在转储之前,必须调用error__reduce_()。加载将按预期工作。

如果需要将大量数据传输到某个外部结构,您可能应该研究如何使用pythons return语句找到解决方案。对我来说,这似乎更清晰/更像蟒蛇。高级异常在Java中大量使用,当使用框架并必须捕获所有可能的错误时,这有时会很烦人。

您应该重写__repr_或__unicode__方法,而不是使用message,构造异常时提供的参数将位于异常对象的args属性中。

使用现代Python异常,您不需要滥用.message或重写__str__()或__repr_()或其中任何一个。如果在引发异常时只需要一条信息性消息,请执行以下操作:

class MyException(Exception):
    pass

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

这将以MyException结尾:我的气垫船上满是鳗鱼。

如果希望从异常中获得更多的灵活性,可以传递字典作为参数:

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