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

发布于 2024-08-03 12:12:19 字数 1018 浏览 6 评论 0原文

在现代 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 中的参数。

更新:两个答案建议重写 __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 fromPEP-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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

你怎么敢 2024-08-10 12:12:19

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

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)

Maybe I missed the question, but why not:

class MyException(Exception):
    pass

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().__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.

In Python 2, you have to use this slightly more complex form of super():

super(ValidationError, self).__init__(message)
嘴硬脾气大 2024-08-10 12:12:19

使用现代 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"])

仍然可以将多个项目传递给异常并通过元组索引访问它们,但这是高度不鼓励的(甚至打算在不久前弃用) )。如果您确实需要多条信息,并且上述方法不足以满足您的需要,那么您应该按照 教程

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
只等公子 2024-08-10 12:12:19

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

这很好,除非您的异常确实是一种更具体的异常:

class MyException(Exception):
    pass

或者更好(也许是完美),而不是 pass 给出文档字符串:

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

中子类化异常子类

文档

异常

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

这意味着如果您的异常是一种更具体的异常,请对该异常进行子类化,而不是通用Exception(结果将是您仍然从派生>Exception 正如文档所建议的那样)。此外,您至少可以提供一个文档字符串(而不是被迫使用 pass 关键字):

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

使用自定义 __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

同样,上面的问题是,为了捕获它,您要么必须专门命名它(如果在其他地方创建,则导入它)或捕获它异常,(但您可能不准备处理所有类型的异常,并且您应该只捕获准备处理的异常)。与下面的批评类似,但另外这不是通过 super 初始化的方式,如果您访问 message 属性,您将收到 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'

"What is the 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 built-in ones are very nice, and your cooperative inheritance ensures that you use them.

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'
北城挽邺 2024-08-10 12:12:19

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

  • 定义一个继承自 Exception基类。这将允许轻松捕获与项目相关的任何异常:

    类 MyProjectError(异常):
        """MyProject 异常的基类。"""
    

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

  • 要创建特定异常,请对异常基类进行子类化。

    类 CustomError(MyProjectError):
       """MyProject 的自定义异常类。"""
    

    您还可以对自定义异常类进行子类化以创建层次结构。

  • 要向自定义异常添加对额外参数的支持,请定义一个具有可变数量参数的 __init__() 方法。调用基类的 __init__(),向其传递任何位置参数(请记住 BaseException/Exception 需要任意数量的位置参数)。将额外的关键字参数存储到实例中,例如:

    类 CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            超级().__init__(*args)
            self.custom_kwarg = kwargs.get('custom_kwarg')
    

    使用示例:

    <前><代码>尝试:
    raise CustomError('发生了不好的事情', custom_kwarg='value')
    除了 CustomError 之外:
    print(f'Сaught CustomError 异常,custom_kwarg={exc.custom_kwarg}')

此设计遵循 里氏替换原则 ,因为您可以用派生异常类的实例替换基异常类的实例。此外,它还允许您创建具有与父类相同参数的派生类的实例。

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

  • Define a base class inheriting from Exception. This will allow to easily catch any exceptions related to the project:

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

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

  • To create a specific exception, subclass the base exception class.

    class CustomError(MyProjectError):
       """A custom exception class for MyProject."""
    

    You can subclass custom exception classes as well to create a hierarchy.

  • To add support for extra argument(s) to a custom exception, define an __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). Store extra keyword arguments to the instance, e.g.:

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

    Usage example:

    try:
        raise CustomError('Something bad happened', custom_kwarg='value')
    except CustomError as exc:
        print(f'Сaught CustomError exception with custom_kwarg={exc.custom_kwarg}')
    

This design 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. Also, it allows you to create an instance of a derived class with the same parameters as the parent.

洒一地阳光 2024-08-10 12:12:19

从 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 对于使其可pickle 很重要。在转储它之前,您必须调用error.__reduce__()。加载将按预期进行。

如果您需要将大量数据传输到某些外部结构,您可能应该研究使用 python return 语句寻找解决方案。这对我来说似乎更清晰/更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.

又怨 2024-08-10 12:12:19

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

>>> 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
空城之時有危險 2024-08-10 12:12:19

您应该重写 __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.

阳光①夏 2024-08-10 12:12:19

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

  • 始终从(至少)继承 Exception。
  • 始终仅使用一个参数调用 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.

两相知 2024-08-10 12:12:19

不,“消息”并不被禁止。它刚刚被弃用。您的应用程序将与使用消息一起正常工作。但当然,您可能希望摆脱弃用错误。

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

如果您的应用程序中有许多异常,那么通常最好为所有异常使用一个通用的自定义基类,以便模块的用户可以执行此操作

try:
    ...
except NelsonsExceptions:
    ...

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

无论如何,如果您执行与 Exception 本身不同的操作,则只需要 __init____str__ 。因为如果弃用,那么您就需要两者,否则您会收到错误。每个类并不需要太多额外的代码。

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

美人如玉 2024-08-10 12:12:19

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

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

    def __repr__(self):
        return self.msg


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

For maximum customisation, to define custom errors, you may want to define an intermediate class that inherits from Exception class as:

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

    def __repr__(self):
        return self.msg


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

梦回梦里 2024-08-10 12:12:19

从 Python 3.9.5 开始,我对上述方法遇到了问题。
但是,我发现这对我有用:

class MyException(Exception):
    """Port Exception"""

然后它可以在如下代码中使用:

try:
    raise MyException('Message')

except MyException as err:
    print (err)

I had issues with the above methods, as of Python 3.9.5.
However, I found that this works for me:

class MyException(Exception):
    """Port Exception"""

And then it could be used in code like:

try:
    raise MyException('Message')

except MyException as err:
    print (err)
海的爱人是光 2024-08-10 12:12:19

可以使用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

这减少了一些样板文件,同时保持了进一步自定义的灵活性。

It's possible to use dataclass to simplify the definition of the custom exception:

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

This reduces some of the boilerplate, while remaining flexible for further customization.

一场春暖 2024-08-10 12:12:19

一个非常简单的方法:

class CustomError(Exception):
    pass

raise CustomError("Hmm, seems like this was custom coded...")

或者,在不打印 __main__ 的情况下引发错误(可能看起来更干净整洁):

class CustomError(Exception):
    __module__ = Exception.__module__

raise CustomError("Improved CustomError!")

A really simple approach:

class CustomError(Exception):
    pass

raise CustomError("Hmm, seems like this was custom coded...")

Or, have the error raise without printing __main__ (may look cleaner and neater):

class CustomError(Exception):
    __module__ = Exception.__module__

raise CustomError("Improved CustomError!")
终难愈 2024-08-10 12:12:19

试试这个例子

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")
耳钉梦 2024-08-10 12:12:19

我遇到了这个线程。这就是我处理自定义异常的方法。虽然 Fault 类稍微复杂,但它使得使用变量参数声明自定义表达异常变得微不足道。

FinalViolationSingletonViolation 都是 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."

遗憾的是,FinalViolationSingletonViolation 只接受 1 个参数。

但人们很容易造成多参数错误,例如

class VesselLoadingError(Fault, BufferError):
    formats = "My {} is full of {}."

raise VesselLoadingError('hovercraft', 'eels')

__main__.VesselLoadingError:我的气垫船里装满了鳗鱼。

I came across this thread. This is how I do custom exceptions. While the Fault class is slightly complex, it makes declaring custom expressive exceptions with variable arguments trivial.

FinalViolation, SingletonViolation are both sub classes of TypeError so will be caught code below.

try:
    <do something>
except TypeError as ex:
    <handler>

That's why Fault doesn't inherit from Exception. To allow derivative exceptions to inherit from the exception of their choice.

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, SingletonViolation unfortunately only accept 1 argument.

But one could easily create a multi arg error e.g.

class VesselLoadingError(Fault, BufferError):
    formats = "My {} is full of {}."

raise VesselLoadingError('hovercraft', 'eels')

__main__.VesselLoadingError: My hovercraft is full of eels.

坏尐絯 2024-08-10 12:12:19

同意@sultanorazbayev的回答。为了简单起见应该使用数据类

import logging
from dataclasses import dataclass

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def main():
    @dataclass
    class MyCustomException(Exception):
        code: int
        message: str
        description: str

    try:
        raise MyCustomException(
            404, 
            "Resource not found", 
            "This resource does not exist or unavailable"
        )
    except MyCustomException as e:
        logger.info(
            f"""
            MyCustomException caught: 
            {e.code} 
            {e.message} 
            {e.description}
        """
        )
    finally:
        logger.info("Cleaning up and exiting...")


if __name__ == "__main__":
    main()

Agreed with @sultanorazbayev's answer. Should use dataclasses for simplicity

import logging
from dataclasses import dataclass

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def main():
    @dataclass
    class MyCustomException(Exception):
        code: int
        message: str
        description: str

    try:
        raise MyCustomException(
            404, 
            "Resource not found", 
            "This resource does not exist or unavailable"
        )
    except MyCustomException as e:
        logger.info(
            f"""
            MyCustomException caught: 
            {e.code} 
            {e.message} 
            {e.description}
        """
        )
    finally:
        logger.info("Cleaning up and exiting...")


if __name__ == "__main__":
    main()
野却迷人 2024-08-10 12:12:19

几个答案都有很好的观点,但我仍在努力清楚地了解 v3.13 中 Python 推荐的自定义异常方法。我还认为其他答案主要涉及该方法的各个方面,而没有对实现和用法的完整描述。

冒着让这个问题变得拥挤的风险,我将我当前的方法(使用 Python v3.10)与使用场景结合起来,试图整理最新的建议。

此示例具有适合 REST API 实现的特征,但可以改变用途。

据我所知,推荐的原则是:

  1. 子类内置 Exception
  2. 使用一个 str 参数调用 super().__init__
  3. 实现 __str__ 控制异常的显示
  4. 为您的特定用例子类化您的自定义异常

此示例不处理子异常的附加参数。对于这个遗漏,我深表歉意。该示例确实支持其他类属性,这些属性无需传递参数即可提供详细信息。

在使用中,我有时会将内置异常转换为自定义异常,以实现处理的一致性。

exceptions.py

"""Exceptions identify application status and primary error message.

Exceptions are used to abort processing
and return execution to the handler whenever processing is blocked.
An Exception is also returned when processing is completed
to indicate HTTP 200 status to caller with or without a raise.

- https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
- https://docs.python.org/3/library/http.html#http.HTTPStatus
"""

import http
import sys
import traceback


class APIException(Exception):
    """Response message and HTTP status. Always subclass, do not use in code."""

    message: str = http.HTTPStatus.IM_A_TEAPOT.phrase  # One standard Exception argument
    code: int = http.HTTPStatus.IM_A_TEAPOT.value  # code is not a parameter

    def __init__(self, message: str = "") -> None:
        """Provide reliably located customisable message."""
        self.message = message or self.message
        super().__init__(self.message)

    def __str__(self) -> str:
        return str(self.code) + ": " + self.message + ": " + self.location(self)

    @staticmethod  # Can pass Exception that are not APIException
    def location(e: Exception | None = None) -> str:
        """Return string with ``traceback`` module and line number e.g. ``"init#67"``."""
        tb = e and e.__traceback__ or sys.exc_info()[-1]  # Use provided or current Exception
        if not tb:  # Exception instances that are not raised do not have a traceback
            return ""
        frame = traceback.extract_tb(tb)[-1]
        return frame[2] + "#" + str(frame[1])


class ServerError(APIException):
    code = http.HTTPStatus.INTERNAL_SERVER_ERROR.value  # HTTP 500
    message = "Server error"


class NotImplemented(APIException):  # noqa: A001: not shadowing builtin
    code = http.HTTPStatus.NOT_IMPLEMENTED.value  # HTTP 501
    message = "Not Implemented"


class BadRequest(APIException):
    code = http.HTTPStatus.BAD_REQUEST.value  # HTTP 400
    message = "Invalid request or parameters"


class NotFound(BadRequest):
    code = http.HTTPStatus.NOT_FOUND.value  # HTTP 404
    message = "Requested calculation not found"


class OK(APIException):
    code = http.HTTPStatus.OK.value  # HTTP 200
    message = http.HTTPStatus.OK.phrase


# vim: set list colorcolumn=121:

为了说明一些常见用法,将应用程序代码包装在 try 块中并处理来自不同来源的各种错误,此示例具有单个 catch 序列和所有路径始终以 status 变量引用的定义 APIException 实例退出。这也可以是成功途径的exceptions.OK。然后,status 始终包含带有 int 状态代码的 status.code 和用于响应描述的 status.message,其中成功响应大多会显示“OK”。

handler.py

try:

    ...

except json.JSONDecodeError as e:
    status = exceptions.BadRequest("JSONDecodeError: " + str(e))
except pydantic.ValidationError as e:
    errors = e.errors()[0]
    msg = ": ".join((str(errors["msg"]), str(errors["loc"]), str(errors["type"])))
    msg = "ValidationError: {}".format(msg)
    status = exceptions.BadRequest(msg)
except exceptions.APIException as e:
    status = e
    status.message = type(e).__name__ + ": " + e.message
except Exception as e:  # noqa: BLE001 do catch all Exception in this case
    traceback.print_exc()  # Allow tracebacks to console
    msg = "Unexpected {}: {}: {}"
    msg = msg.format(type(e).__name__, str(e), exceptions.APIException.location(e))
    status = exceptions.ServerError(msg)

...

注意如何在类中使用 APIException location 静态方法从其他异常返回统一字符串:

exceptions.APIException.location(<any Exception>)

There are good points in several answers but I'm still working on getting a clear understanding of what the Python recommended approach to customised exceptions is in v3.13. I also feel the other answers mostly address aspects of the approach without a full description of implementation and usage.

At the risk of crowding this question I'm putting my current approach (using Python v3.10) with a usage scenario to attempt to collate the most recent advice.

This example has characteristics that suit a REST API implementation but can be repurposed.

The recommended principles as far as I can determine are:

  1. Subclass builtin Exception
  2. Call super().__init__ with one str argument
  3. Implement __str__ to control display of the exceptions
  4. Subclass your customised exception for your particular use cases

This example does not handle additional parameters to the child exceptions. Sorry for this omission. The example does support additional class attributes which provide detail without needing to pass parameters.

In usage I sometimes convert built-in exceptions to custom exceptions for uniformity of processing.

exceptions.py:

"""Exceptions identify application status and primary error message.

Exceptions are used to abort processing
and return execution to the handler whenever processing is blocked.
An Exception is also returned when processing is completed
to indicate HTTP 200 status to caller with or without a raise.

- https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
- https://docs.python.org/3/library/http.html#http.HTTPStatus
"""

import http
import sys
import traceback


class APIException(Exception):
    """Response message and HTTP status. Always subclass, do not use in code."""

    message: str = http.HTTPStatus.IM_A_TEAPOT.phrase  # One standard Exception argument
    code: int = http.HTTPStatus.IM_A_TEAPOT.value  # code is not a parameter

    def __init__(self, message: str = "") -> None:
        """Provide reliably located customisable message."""
        self.message = message or self.message
        super().__init__(self.message)

    def __str__(self) -> str:
        return str(self.code) + ": " + self.message + ": " + self.location(self)

    @staticmethod  # Can pass Exception that are not APIException
    def location(e: Exception | None = None) -> str:
        """Return string with ``traceback`` module and line number e.g. ``"init#67"``."""
        tb = e and e.__traceback__ or sys.exc_info()[-1]  # Use provided or current Exception
        if not tb:  # Exception instances that are not raised do not have a traceback
            return ""
        frame = traceback.extract_tb(tb)[-1]
        return frame[2] + "#" + str(frame[1])


class ServerError(APIException):
    code = http.HTTPStatus.INTERNAL_SERVER_ERROR.value  # HTTP 500
    message = "Server error"


class NotImplemented(APIException):  # noqa: A001: not shadowing builtin
    code = http.HTTPStatus.NOT_IMPLEMENTED.value  # HTTP 501
    message = "Not Implemented"


class BadRequest(APIException):
    code = http.HTTPStatus.BAD_REQUEST.value  # HTTP 400
    message = "Invalid request or parameters"


class NotFound(BadRequest):
    code = http.HTTPStatus.NOT_FOUND.value  # HTTP 404
    message = "Requested calculation not found"


class OK(APIException):
    code = http.HTTPStatus.OK.value  # HTTP 200
    message = http.HTTPStatus.OK.phrase


# vim: set list colorcolumn=121:

To illustrate some common usage, wrapping application code in a try block and handling a variety of errors from different sources, this example has a single catch sequence and all pathways always exit with a defining APIException instance referred to by the status variable. This can also be exceptions.OK for the success pathway. status then always contains status.code with an int status code and status.message for the response description, which will mostly be "OK" for successful responses.

handler.py:

try:

    ...

except json.JSONDecodeError as e:
    status = exceptions.BadRequest("JSONDecodeError: " + str(e))
except pydantic.ValidationError as e:
    errors = e.errors()[0]
    msg = ": ".join((str(errors["msg"]), str(errors["loc"]), str(errors["type"])))
    msg = "ValidationError: {}".format(msg)
    status = exceptions.BadRequest(msg)
except exceptions.APIException as e:
    status = e
    status.message = type(e).__name__ + ": " + e.message
except Exception as e:  # noqa: BLE001 do catch all Exception in this case
    traceback.print_exc()  # Allow tracebacks to console
    msg = "Unexpected {}: {}: {}"
    msg = msg.format(type(e).__name__, str(e), exceptions.APIException.location(e))
    status = exceptions.ServerError(msg)

...

Notice how the APIException location static method can be used from the class to return a uniform string from other exceptions:

exceptions.APIException.location(<any Exception>)
十六岁半 2024-08-10 12:12:19

对我来说,它只是 __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 !@#
amp;*" }

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 !@#
amp;*
__main__.localbreak: False

For me it is just __init__ and variables but making sometimes testing.

My sample:

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 !@#
amp;*" }

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

Output:

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 !@#
amp;*
__main__.localbreak: False
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文