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

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

在现代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__。好像要打字很多,有必要吗?

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.

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.

I was tripped up by the following deprecation warning in 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.

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.

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


回答 0

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

class MyException(Exception):
    pass

编辑:要覆盖某些内容(或传递额外的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

这样,您可以将错误消息的字典传递给第二个参数,并在以后使用 e.errors


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

Maybe I missed the question, but why not:

class MyException(Exception):
    pass

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

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


Python 3 Update: In Python 3+, you can use this slightly more compact use of 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

回答 1

随着现代Python的exceptions,你并不需要滥用.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

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:

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:

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.

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

回答 2

“在现代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'''

设置可使用custom创建自己的属性__init__。避免将dict作为位置参数传递,以后您的代码用户将感谢您。如果您使用不推荐使用的message属性,则自行分配该属性将避免出现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(但您可能不准备处理所有类型的Exception,并且您应该只捕获您准备处理的异常)。与以下内容类似的批评,但除此之外,这不是通过进行初始化的方式superDeprecationWarning如果您访问message属性,则会得到一个:

编辑:要覆盖某些内容(或传递额外的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

这样,您可以将错误消息的字典传递给第二个参数,并在以后使用e.errors到达它。

它也需要传入两个参数(self。除外)。这是一个有趣的约束,将来的用户可能不会欣赏。

直截了当-它违反了Liskov的可替代性

我将演示两个错误:

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

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

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

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:

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:

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

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

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.

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'

回答 3

见异常缺省情况下是如何工作的,如果一个VS多个属性使用(回溯略):

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

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:

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

and you’ll have

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

回答 4

从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__()。加载将按预期工作。

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

As of Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html), the recommended method is still:

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

Please don’t forget to document, why a custom exception is neccessary!

If you need to, this is the way to go for exceptions with more data:

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

and fetch them like:

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 is important to make it pickle-able. Before dumping it, you have to call error.__reduce__(). Loading will work as expected.

You maybe should investigate in finding a solution using pythons return statement if you need much data to be transferred to some outer structure. This seems to be clearer/more pythonic to me. Advanced exceptions are heavily used in Java, which can sometimes be annoying, when using a framework and having to catch all possible errors.


回答 5

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

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.


回答 6

不,“消息”不是禁止的。只是过时了。您的应用程序可以正常使用消息。但是,您当然可以摆脱折旧错误。

当为应用程序创建自定义Exception类时,它们中的许多不仅仅从Exception继承子类,还从ValueError之类的其他子类继承子类。然后,您必须适应它们对变量的使用。

而且,如果您的应用程序中有很多异常,通常最好为所有异常都拥有一个通用的自定义基类,以便模块的用户可以

try:
    ...
except NelsonsExceptions:
    ...

在这种情况下,您可以在__init__ and __str__那里进行所需的操作,因此您不必为每个异常重复执行该操作。但是简单地调用message变量而不是message可以解决问题。

无论如何,__init__ or __str__如果您做的事情与Exception本身不同,则仅需要使用。并且因为如果不赞成使用,那么您将同时需要两者,否则您将得到一个错误。每个类不需要很多额外的代码。;)

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.

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.

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. ;)


回答 7

请参阅非常好的文章“ Python异常的权威指南 ”。基本原则是:

  • 始终从(至少)继承。
  • 始终BaseException.__init__仅使用一个参数进行调用。
  • 构建库时,请定义从Exception继承的基类。
  • 提供有关错误的详细信息。
  • 从内置异常类型继承是有意义的。

还有关于组织(在模块中)和包装异常的信息,我建议阅读指南。

See a very good article “The definitive guide to Python exceptions“. The basic principles are:

  • Always inherit from (at least) Exception.
  • Always call BaseException.__init__ with only one argument.
  • When building a library, define a base class inheriting from Exception.
  • Provide details about the error.
  • Inherit from builtin exceptions types when it makes sense.

There is also information on organizing (in modules) and wrapping exceptions, I recommend to read the guide.


回答 8

试试这个例子

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

Try this Example

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

回答 9

要正确定义自己的异常,需要遵循一些最佳实践:

  • 定义一个继承自的基类Exception。这将允许捕获与项目相关的任何异常(更具体的异常应从该项目继承):

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""

    在单独的模块(例如exceptions.py)中组织这些异常类通常是一个好主意。

  • 要将额外的参数传递给您的异常,请定义一个__init__()具有可变数量参数的自定义方法。调用基类__init__()将任何位置参数传递给它(记住BaseException/Exception期望有任意数量的位置参数):

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super(CustomError, self).__init__(*args)
            self.foo = kwargs.get('foo')

    要使用额外的参数引发此类异常,可以使用:

    raise CustomError('Something bad happened', foo='foo')

    这样的设计居然坚持里氏替换原则,因为你可以用一个派生的异常类的实例代替基本异常类的实例。

To define your own exceptions correctly, there are a few best practices that you need to follow:

  • Define a base class inheriting from Exception. This will allow to catch any exception related to the project (more specific exceptions should inherit from it):

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""
    

    Organizing these exception classes in a separate module (e.g. exceptions.py) is generally a good idea.

  • To pass extra argument(s) to your exception, define a custom __init__() method with a variable number of arguments. Call the base class’s __init__() passing any positional arguments to it (remember that BaseException/Exception expect any number of positional arguments):

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super(CustomError, self).__init__(*args)
            self.foo = kwargs.get('foo')
    

    To raise such exception with an extra argument you can use:

    raise CustomError('Something bad happened', foo='foo')
    

    This design actually adheres to the Liskov substitution principle, since you can replace an instance of a base exception class with an instance of a derived exception class.