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

时间:2022-09-06 19:26:08

What's the proper way to declare custom exception classes in modern Python? My primary goal is to follow whatever standard other exception classes have, so that (for instance) any extra string I include in the exception is printed out by whatever tool caught the exception.

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

By "modern Python" I mean something that will run in Python 2.5 but be 'correct' for the Python 2.6 and Python 3.* way of doing things. And by "custom" I mean an Exception object that can include extra data about the cause of the error: a string, maybe also some other arbitrary object relevant to the exception.

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

I was tripped up by the following deprecation warning in Python 2.6.2:

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

It seems crazy that BaseException has a special meaning for attributes named message. I gather from PEP-352 that attribute did have a special meaning in 2.5 they're trying to deprecate away, so I guess that name (and that one alone) is now forbidden? Ugh.

BaseException对于名为message的属性有特殊的含义,这似乎很疯狂。我从PEP-352收集到,这个属性在2.5中有一个特殊的意思,他们正在试图反对,所以我猜这个名字(和那个)现在被禁止了?啊。

I'm also fuzzily aware that Exception has some magic parameter args, but I've never known how to use it. Nor am I sure it's the right way to do things going forward; a lot of the discussion I found online suggested they were trying to do away with args in Python 3.

我也模糊地意识到Exception有一些神奇的参数args,但是我从来都不知道如何使用它。我也不确定这是一种正确的做事方式;我在网上找到的许多讨论都表明,他们试图用Python 3中的args来解决问题。

Update: two answers have suggested overriding __init__, and __str__/__unicode__/__repr__. That seems like a lot of typing, is it necessary?

更新:两个答案建议重写__init__和__str__/__unicode__/__repr__。看起来打字很多,有必要吗?

6 个解决方案

#1


895  

Maybe I missed the question, but why not:

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

class MyException(Exception):
    pass

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

编辑:要覆盖某些内容(或传递额外的args),请这样做:

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

通过这种方式,您可以将错误消息通知传递给第二个param,并在稍后使用e.errors处理它


Python 3 Update: In Python 3+, you can use this slightly more compact use of super():

Python 3更新:在Python 3+中,您可以使用super()的这种更紧凑的用法:

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

#2


352  

With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:

对于现代Python异常,您不需要滥用.message,或override .__str__()或.__repr__()或其中任何一个。如果你想要的只是在你的异常被提出的时候提供一个信息,那就这样做:

class MyException(Exception):
    pass

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

That will give a traceback ending with MyException: My hovercraft is full of eels.

这就给了我一个例外:我的气垫船装满了鳗鱼。

If you want more flexibility from the exception, you could pass a dictionary as the argument:

如果您希望从异常中获得更大的灵活性,您可以通过字典作为参数:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:

但是,要在一个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"])

It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.

仍然有可能将多个项目传递给异常并通过tuple索引访问它们,但这是非常不鼓励的(以前甚至打算弃用它们)。如果您确实需要不止一个信息,而上面的方法对您来说是不够的,那么您应该按照教程中描述的子类化异常。

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

#3


145  

"Proper way to declare custom exceptions in modern Python?"

This is fine, unless your exception is really a type of a more specific exception:

这是可以的,除非你的例外是一种更具体的例外:

class MyException(Exception):
    pass

Or better (maybe perfect), instead of pass give a docstring:

或者更好(可能是完美的),而不是传递一个docstring:

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

Subclassing Exception Subclasses

From the docs

从文档

Exception

异常

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

所有内置的、非系统退出的异常都来自这个类。所有用户定义的异常也应该从这个类派生。

That means that if your exception is a type of a more specific exception, subclass that exception instead of the generic Exception (and the result will be that you still derive from Exception as the docs recommend). Also, you can at least provide a docstring (and not be forced to use the pass keyword):

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

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

Set attributes you create yourself with a custom __init__. Avoid passing a dict as a positional argument, future users of your code will thank you. If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning:

设置您使用自定义__init__创建的属性。避免传递一个位置参数的命令,代码的未来用户将感谢您。如果您使用已弃用的消息属性,那么您自己分配它将避免一个弃用警告:

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) 

There's really no need to write your own __str__ or __repr__. The builtin ones are very nice, and your cooperative inheritance ensures that you use it.

真的没有必要写你自己的简历或简历。内置的非常好,您的合作继承确保您使用它。

Critique of the top answer

Maybe I missed the question, but why not:

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

class MyException(Exception):
    pass

Again, the problem with the above is that in order to catch it, you'll either have to name it specifically (importing it if created elsewhere) or catch Exception, (but you're probably not prepared to handle all types of Exceptions, and you should only catch exceptions you are prepared to handle). Similar criticism to the below, but additionally that's not the way to initialize via super, and you'll get a DeprecationWarning if you access the message attribute:

同样,上面的问题是,为了捕获它,您必须指定它(在其他地方创建时导入它)或者捕获异常(但是您可能不准备处理所有类型的异常,并且您应该只捕获您准备处理的异常)。类似于下面的批评,但是另外,这不是通过super初始化的方法,如果您访问message属性,您将得到一个弃用警告:

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

编辑:要覆盖某些内容(或传递额外的args),请这样做:

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

通过这种方式,您可以将错误消息通知传递给第二个param,并在稍后使用e.errors处理它

It also requires exactly two arguments to be passed in (aside from the self.) No more, no less. That's an interesting constraint that future users may not appreciate.

它还需要传入两个参数(除了self)。不能多也不能少。这是一个有趣的限制,未来的用户可能不会欣赏。

To be direct - it violates Liskov substitutability.

直接地说,它违反了利斯科夫的可替代性。

I'll demonstrate both errors:

我将演示两个错误:

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

Compared to:

相比:

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

#4


37  

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

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

>>> raise Exception('bad thing happened')
Exception: bad thing happened

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

so you might want to have a sort of "exception template", working as an exception itself, in a compatible way:

因此,您可能想要有一种“异常模板”,作为异常本身,以一种兼容的方式工作:

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

this can be done easily with this subclass

这可以很容易地在这个子类中完成。

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

and if you don't like that default tuple-like representation, just add __str__ method to the ExceptionTemplate class, like:

如果您不喜欢默认的类tuplelike表示,那么只需向ExceptionTemplate类添加__str__方法,比如:

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

and you'll have

和你会有

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

#5


14  

You should override __repr__ or __unicode__ methods instead of using message, the args you provide when you construct the exception will be in the args attribute of the exception object.

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

#6


5  

No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.

不,“信息”是不被禁止的。它只是弃用。您的应用程序可以很好地使用消息。当然,您可能想要消除弃用错误。

When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.

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

And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do

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

try:
    ...
except NelsonsExceptions:
    ...

And in that case you can do the __init__ and __str__ needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.

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

In any case, you only need the __init__ or __str__ if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class. ;)

无论如何,您只需要__init__或__str__做一些与异常本身不同的事情。因为如果是弃用,你需要两个,否则你会得到一个错误。这并不是每个类需要的全部额外代码。,)

#1


895  

Maybe I missed the question, but why not:

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

class MyException(Exception):
    pass

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

编辑:要覆盖某些内容(或传递额外的args),请这样做:

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

通过这种方式,您可以将错误消息通知传递给第二个param,并在稍后使用e.errors处理它


Python 3 Update: In Python 3+, you can use this slightly more compact use of super():

Python 3更新:在Python 3+中,您可以使用super()的这种更紧凑的用法:

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

#2


352  

With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:

对于现代Python异常,您不需要滥用.message,或override .__str__()或.__repr__()或其中任何一个。如果你想要的只是在你的异常被提出的时候提供一个信息,那就这样做:

class MyException(Exception):
    pass

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

That will give a traceback ending with MyException: My hovercraft is full of eels.

这就给了我一个例外:我的气垫船装满了鳗鱼。

If you want more flexibility from the exception, you could pass a dictionary as the argument:

如果您希望从异常中获得更大的灵活性,您可以通过字典作为参数:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:

但是,要在一个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"])

It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.

仍然有可能将多个项目传递给异常并通过tuple索引访问它们,但这是非常不鼓励的(以前甚至打算弃用它们)。如果您确实需要不止一个信息,而上面的方法对您来说是不够的,那么您应该按照教程中描述的子类化异常。

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

#3


145  

"Proper way to declare custom exceptions in modern Python?"

This is fine, unless your exception is really a type of a more specific exception:

这是可以的,除非你的例外是一种更具体的例外:

class MyException(Exception):
    pass

Or better (maybe perfect), instead of pass give a docstring:

或者更好(可能是完美的),而不是传递一个docstring:

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

Subclassing Exception Subclasses

From the docs

从文档

Exception

异常

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

所有内置的、非系统退出的异常都来自这个类。所有用户定义的异常也应该从这个类派生。

That means that if your exception is a type of a more specific exception, subclass that exception instead of the generic Exception (and the result will be that you still derive from Exception as the docs recommend). Also, you can at least provide a docstring (and not be forced to use the pass keyword):

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

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

Set attributes you create yourself with a custom __init__. Avoid passing a dict as a positional argument, future users of your code will thank you. If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning:

设置您使用自定义__init__创建的属性。避免传递一个位置参数的命令,代码的未来用户将感谢您。如果您使用已弃用的消息属性,那么您自己分配它将避免一个弃用警告:

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) 

There's really no need to write your own __str__ or __repr__. The builtin ones are very nice, and your cooperative inheritance ensures that you use it.

真的没有必要写你自己的简历或简历。内置的非常好,您的合作继承确保您使用它。

Critique of the top answer

Maybe I missed the question, but why not:

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

class MyException(Exception):
    pass

Again, the problem with the above is that in order to catch it, you'll either have to name it specifically (importing it if created elsewhere) or catch Exception, (but you're probably not prepared to handle all types of Exceptions, and you should only catch exceptions you are prepared to handle). Similar criticism to the below, but additionally that's not the way to initialize via super, and you'll get a DeprecationWarning if you access the message attribute:

同样,上面的问题是,为了捕获它,您必须指定它(在其他地方创建时导入它)或者捕获异常(但是您可能不准备处理所有类型的异常,并且您应该只捕获您准备处理的异常)。类似于下面的批评,但是另外,这不是通过super初始化的方法,如果您访问message属性,您将得到一个弃用警告:

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

编辑:要覆盖某些内容(或传递额外的args),请这样做:

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

通过这种方式,您可以将错误消息通知传递给第二个param,并在稍后使用e.errors处理它

It also requires exactly two arguments to be passed in (aside from the self.) No more, no less. That's an interesting constraint that future users may not appreciate.

它还需要传入两个参数(除了self)。不能多也不能少。这是一个有趣的限制,未来的用户可能不会欣赏。

To be direct - it violates Liskov substitutability.

直接地说,它违反了利斯科夫的可替代性。

I'll demonstrate both errors:

我将演示两个错误:

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

Compared to:

相比:

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

#4


37  

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

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

>>> raise Exception('bad thing happened')
Exception: bad thing happened

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

so you might want to have a sort of "exception template", working as an exception itself, in a compatible way:

因此,您可能想要有一种“异常模板”,作为异常本身,以一种兼容的方式工作:

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

this can be done easily with this subclass

这可以很容易地在这个子类中完成。

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

and if you don't like that default tuple-like representation, just add __str__ method to the ExceptionTemplate class, like:

如果您不喜欢默认的类tuplelike表示,那么只需向ExceptionTemplate类添加__str__方法,比如:

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

and you'll have

和你会有

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

#5


14  

You should override __repr__ or __unicode__ methods instead of using message, the args you provide when you construct the exception will be in the args attribute of the exception object.

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

#6


5  

No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.

不,“信息”是不被禁止的。它只是弃用。您的应用程序可以很好地使用消息。当然,您可能想要消除弃用错误。

When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.

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

And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do

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

try:
    ...
except NelsonsExceptions:
    ...

And in that case you can do the __init__ and __str__ needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.

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

In any case, you only need the __init__ or __str__ if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class. ;)

无论如何,您只需要__init__或__str__做一些与异常本身不同的事情。因为如果是弃用,你需要两个,否则你会得到一个错误。这并不是每个类需要的全部额外代码。,)