简体   繁体   English

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

[英]Proper way to declare custom exceptions in modern Python?

What's the proper way to declare custom exception classes in modern Python?在现代 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.我所说的“现代 Python”是指将在 Python 2.5 中运行但对于 Python 2.6 和 Python 3.* 做事方式“正确”的东西。 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. “自定义”是指一个Exception对象,它可以包含有关错误原因的额外数据:一个字符串,也可能是与异常相关的一些其他任意对象。

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 . BaseException对名为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?我从PEP-352中收集到,该属性在 2.5 中确实具有特殊含义,他们正试图弃用,所以我猜这个名称(以及那个名称)现在被禁止了? Ugh.啊。

I'm also fuzzily aware that Exception has some magic parameter args , but I've never known how to use it.我也模糊地意识到Exception有一些神奇的参数args ,但我从来不知道如何使用它。 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.我在网上找到的很多讨论表明他们试图在 Python 3 中取消 args。

Update: two answers have suggested overriding __init__ , and __str__ / __unicode__ / __repr__ .更新:两个答案建议覆盖__init____str__ / __unicode__ / __repr__ That seems like a lot of typing, is it necessary?这似乎是很多打字,有必要吗?

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 .这样,您可以将错误消息的 dict 传递给第二个参数,然后使用e.errors获取它。

In Python 2, you have to use this slightly more complex form of super() :在 Python 2 中,您必须使用这种稍微复杂的super()形式:

super(ValidationError, self).__init__(message)

With modern Python Exceptions, you don't need to abuse .message , or override .__str__() or .__repr__() or any of it.使用现代 Python 异常,您无需滥用.message或覆盖.__str__().__repr__()或其中任何一个。 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 .这将给出一个以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.但是,在except块中获取这些细节有点复杂。 The details are stored in the args attribute, which is a list.详细信息存储在args属性中,该属性是一个列表。 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 .如果您确实需要多条信息并且上述方法对您来说还不够,那么您应该按照教程中的描述子类化Exception

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

"What is the proper way to declare custom exceptions in modern Python?" “在现代 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:或者更好(也许是完美的),而不是pass给一个文档字符串:

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).这意味着,如果您的异常是一种更具体的异常,请将该异常子类化而不是通用Exception (结果将是您仍然按照文档的建议从Exception派生)。 Also, you can at least provide a docstring (and not be forced to use the pass keyword):此外,您至少可以提供一个文档字符串(而不是被迫使用pass关键字):

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

Set attributes you create yourself with a custom __init__ .使用自定义__init__设置您自己创建的属性。 Avoid passing a dict as a positional argument, future users of your code will thank you.避免将 dict 作为位置参数传递,您的代码的未来用户会感谢您。 If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning :如果您使用不推荐使用的消息属性,则自己分配它将避免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__ .真的没有必要编写自己的__str____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:与以下类似的批评,但另外这不是通过super初始化的方式,如果您访问 message 属性,您将获得DeprecationWarning

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这样,您可以将错误消息的 dict 传递给第二个参数,然后使用 e.errors 获取它

It also requires exactly two arguments to be passed in (aside from the self .) No more, no less.它还需要传入两个参数(除了self )。不多也不少。 That's an interesting constraint that future users may not appreciate.这是一个有趣的约束,未来的用户可能不会喜欢。

To be direct - it violates Liskov substitutability .直截了当——它违反了Liskov 可替代性

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'

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:如果您不喜欢默认的类似元组的表示,只需将__str__方法添加到ExceptionTemplate类,例如:

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

and you'll have你会有

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

As of Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), the recommended method is still:截至 Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ),推荐的方法仍然是:

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. payload=None对于使其可以腌制很重要。 Before dumping it, you have to call error.__reduce__() .在转储它之前,你必须调用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.如果您需要将大量数据传输到某个外部结构,您可能应该调查使用 pythons return语句找到解决方案。 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. Java 中大量使用高级异常,当使用框架并且必须捕获所有可能的错误时,这有时会很烦人。

To define your own exceptions correctly, there are a few best practices that you should follow:要正确定义您自己的异常,您应该遵循一些最佳实践:

  • Define a base class inheriting from Exception .定义一个继承自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 (eg exceptions.py ) is generally a good idea.在单独的模块中组织异常类(例如exceptions.py )通常是一个好主意。

  • 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.要为自定义异常添加对额外参数的支持,请定义具有可变数量参数的__init__()方法。 Call the base class's __init__() , passing any positional arguments to it (remember that BaseException / Exception expect any number of positional arguments ).调用基类的__init__() ,将任何位置参数传递给它(记住BaseException / Exception需要任意数量的位置参数)。 Store extra argument(s) to the instance, eg:将额外的参数存储到实例中,例如:

     class CustomError(MyProjectError): def __init__(self, *args, **kwargs): super().__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 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.此设计遵循Liskov 替换原则,因为您可以将基异常类的实例替换为派生异常类的实例。 Also, it allows you to create an instance of a derived class with the same parameters as the parent.此外,它还允许您创建具有与父类相同参数的派生类的实例。

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

See a very good article " The definitive guide to Python exceptions ".请参阅一篇非常好的文章“ Python 异常的权威指南”。 The basic principles are:基本原则是:

  • Always inherit from (at least) Exception.总是从(至少)异常继承。
  • Always call BaseException.__init__ with only one argument.始终只使用一个参数调用BaseException.__init__
  • When building a library, define a base class inheriting from Exception.在构建库时,定义一个继承自 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.还有关于组织(在模块中)和包装异常的信息,我建议阅读指南。

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.当您为应用程序创建自定义 Exception 类时,它们中的许多不仅是 Exception 的子类,而是来自其他的子类,例如ValueError或类似的。 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.在这种情况下,您可以在此处执行所需的__init____str__ ,因此您不必为每个异常都重复它。 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.在任何情况下,如果你做的事情与 Exception 本身不同,你只需要__init____str__ 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.这并不是每个班级所需的大量额外代码。

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

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):或者,在不打印__main__的情况下引发错误(可能看起来更整洁):

class CustomError(Exception):
    __module__ = Exception.__module__

raise CustomError("Improved CustomError!")

I had issues with the above methods, as of Python 3.9.5.从 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)

try this尝试这个

class error(Exception):
 
    def __init__(self, value):
        self.value = value
 
    def __str__(self):
        return(repr(self.value))
def check_int(num):
         if type(num) == int:
            print(True)
         else:
            raise error('must be integer')
check_int('')#this will raise our custom error because we provided str or if value is int it will print true

if you run check_int(7) , you will get如果你运行check_int(7) ,你会得到

True

else you will get否则你会得到

Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 13, in <module>
  File "<string>", line 12, in check_int
__main__.error: 'must be integer'

if you have this as a package you will get如果你有这个作为一个包,你会得到
i saved test/__init__.py in site packages我在站点包中保存了test/__init__.py

Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 1, in <module>
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.9/site-packages/test/__init__.py", line 13, in <module>
    check_int('err')
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.9/site-packages/test/__init__.py", line 12, in check_int
    raise error('must be integer')
test.error: 'must be integer'

is this you wanted?这是你想要的吗?

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.虽然Fault类稍微复杂一些,但它使得使用可变参数声明自定义表达异常变得微不足道。

FinalViolation , SingletonViolation are both sub classes of TypeError so will be caught code below. FinalViolationSingletonViolation都是TypeError的子类,因此将在下面捕获代码。

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

That's why Fault doesn't inherit from Exception .这就是为什么Fault不继承自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. FinalViolation , SingletonViolation很遗憾只接受 1 个参数。

But one could easily create a multi arg error eg但是可以很容易地创建一个多参数错误,例如

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

raise VesselLoadingError('hovercraft', 'eels')

__main__.VesselLoadingError: My hovercraft is full of eels.

For maximum customisation, to define custom errors, you may want to define an intermediate class that inherits from Exception class as:为了最大程度地自定义,要定义自定义错误,您可能需要定义一个继承自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 me it is just __init__ and variables but making sometimes testing.对我来说,它只是__init__和变量,但有时会进行测试。

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 !@#$&*" }

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM