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

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

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

我在Python 2.6.2中遇到了下面的弃用警告:

1
2
3
4
5
6
>>> 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,但是我从来不知道如何使用它。我也不确定这是向前发展的正确方式;我在网上找到的很多讨论都表明,他们正试图在python3中取消args。

更新:有两个答案建议重写__init____str__/__unicode__/__repr__。好像打了很多字,有必要吗?


也许我错过了这个问题,但为什么不呢:

1
2
class MyException(Exception):
    pass

编辑:要覆盖某些东西(或传递额外的参数),请这样做:

1
2
3
4
5
6
7
8
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进行处理

Python 3更新:在python3 +中,您可以使用稍微紧凑一点的super():

1
2
3
4
5
6
7
8
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


对于现代Python异常,您不需要滥用.message或覆盖.__str__().__repr__()或任何其他异常。如果您想要的只是在抛出异常时得到一条有用的消息,请这样做:

1
2
3
4
class MyException(Exception):
    pass

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

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

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

1
2
3
4
5
raise MyException({"message":"My hovercraft is full of animals","animal":"eels
<div class="
suo-content">[collapse title=""]<ul><li>"但这将在未来被弃用"——这仍然是为了弃用吗?Python 3.7似乎仍然乐于接受<wyn>Exception(foo, bar, qux)</wyn>。</li><li>自从上次尝试由于转换的痛苦而失败后,它还没有看到任何最近的工作来破坏它,但是这种用法仍然不受欢迎。我将更新我的答案来反映这一点。</li><li>@frnknstn,为什么不鼓励这样做?对我来说,这是个不错的习语。</li><li>首先,使用元组存储异常信息与使用字典存储异常信息相比没有什么好处。如果您对异常更改背后的原因感兴趣,请查看PEP352</li></ul>[/collapse]</div><p><center>[wp_ad_camp_1]</center></p><hr><blockquote>
  <hh1>"
Proper way to declare custom exceptions in modern Python?"</hh1>
</blockquote><p>这很好,除非你的异常是一种更特殊的异常:</P>[cc lang="
python"]class MyException(Exception):
    pass

或者更好(也许是完美的),而不是pass给出一个docstring:

1
2
class MyException(Exception):
   """Raise for my specific kind of exception"""

<

子类化异常子类/ hh2 >

从文档

Exception

All built-in, non-system-exiting exceptions are derived from this class.
All user-defined exceptions should also be derived from this
class.

这意味着,如果您的异常是一种更具体的异常类型,则子类化该异常而不是通用的Exception(结果将是您仍然按照文档的建议从Exception派生)。此外,您至少可以提供一个docstring(而不必强制使用pass关键字):

1
2
class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

设置使用自定义__init__创建的属性。避免将dict作为位置参数传递,代码的未来用户将会感谢您。如果您使用了deprecated message属性,那么您自己分配它将避免使用DeprecationWarning:

1
2
3
4
5
6
7
8
9
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__。内置组件非常好,您的合作继承确保您使用它。

顶部答案的评论

Maybe I missed the question, but why not:

1
2
class MyException(Exception):
    pass

同样,上面的问题是,为了捕获它,您要么必须指定它的名称(如果在其他地方创建,则需要导入它),要么捕获异常(但是您可能不准备处理所有类型的异常,您应该只捕获准备处理的异常)。类似的批评如下,但除此之外,这不是通过super初始化的方法,如果你访问message属性,你会得到一个DeprecationWarning:

Edit: to override something (or pass extra args), do this:

1
2
3
4
5
6
7
8
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

That way you could pass dict of error messages to the second param, and get to it later with e.errors

它还需要传入两个参数(除了self)。不多不少。这是一个有趣的约束,未来的用户可能不会喜欢。

直接地说,它违反了Liskov可替换性。

我将演示这两个错误:

1
2
3
4
5
6
7
8
9
10
>>> 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'

相比:

1
2
>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'


see how exceptions work by default if one vs more attributes are used (tracebacks omitted):

1
2
3
4
5
>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

所以你可能想要一种"异常模板",作为一个异常本身,以兼容的方式工作:

1
2
3
4
5
6
7
8
9
>>> 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')

使用这个子类可以很容易地做到这一点

1
2
3
4
5
class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

如果您不喜欢默认的类元表示,只需将__str__方法添加到ExceptionTemplate类中,比如:

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

和你会有

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

您应该覆盖__repr____unicode__方法,而不是使用message,构造异常时提供的args将位于异常对象的args属性中。


不,"消息"不是禁止的。它只是弃用。您的应用程序可以很好地使用消息。当然,您可能希望消除弃用错误。

当您为您的应用程序创建自定义异常类时,它们中的许多并不仅仅来自Exception,而是来自其他异常,如ValueError或类似的异常。然后你必须适应他们对变量的使用。

如果您的应用程序中有很多异常,通常最好为所有异常都提供一个通用的自定义基类,这样模块的用户就可以这样做

1
2
3
4
try:
    ...
except NelsonsExceptions:
    ...

在这种情况下,您可以执行所需的__init__ and __str__,因此不必对每个异常都重复它。但是简单地调用message变量而不是message就可以了。

在任何情况下,您只需要在执行与Exception本身不同的操作时使用__init__ or __str__。因为如果不赞成,你需要两者,否则你会得到一个错误。每个类需要的额外代码并不多。,)


从Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html)开始,推荐的方法仍然是:

1
2
3
class CustomExceptionName(Exception):
   """Exception raised when very uncommon things happen"""
    pass

请不要忘记记录,为什么需要自定义异常!

如果你需要,这是处理异常的方法与更多的数据:

1
2
3
4
5
6
7
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

然后像这样取:

1
2
3
4
5
try:
    raise CustomExceptionName("Very bad mistake.","Forgot upgrading from Python 1")
except CustomExceptionName as error:
    print(str(error)) # Very bad mistake
    print("Detail: {}".format(self.payload)) # Detail: Forgot upgrading from Python 1

payload=None使其可酸洗是很重要的。在丢弃它之前,您必须调用error.__reduce()__。加载将按预期工作。

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


试试这个例子

1
2
3
4
5
6
7
8
9
10
11
12
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")