首页 文章

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

提问于
浏览
980

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

通过“现代Python”,我的意思是在Python 2.5中运行,但对于Python 2.6和Python 3 *做事的方式是“正确的” . 而“custom”我指的是一个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__ 两个答案 . 这似乎很多打字,是否有必要?

8 回答

  • 6

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

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

    所以你可能希望有一种“ exception template ”,以兼容的方式作为例外本身:

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

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

    class MyException(Exception):
        pass
    

    Edit: 覆盖某些东西(或传递额外的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
    

    这样你就可以将错误消息的dict传递给第二个参数,然后用 e.errors 来获取它 .


    Python 3 Update: 在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
    
  • 41

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

    仍然可以将多个项目传递给异常并通过元组索引访问它们,但这是非常不鼓励的(甚至打算暂时弃用) . 如果您确实需要多条信息并且上述方法对您来说还不够,那么您应该按照tutorial中的描述继承 Exception .

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

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

  • 1008

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

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

    class MyException(Exception):
        pass
    

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

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

    子类化异常子类

    来自docs

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

    这意味着 if 您的异常是一种更具体的异常,子类是异常而不是泛型 Exception (结果将是您仍然从文档推荐的 Exception 派生) . 此外,您至少可以提供docstring(而不是强制使用 pass 关键字):

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

    使用自定义 __init__ 设置您自己创建的属性 . 避免传递dict作为位置参数,未来的代码用户会感谢你 . 如果您使用已弃用的消息属性,则自行分配将避免 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__ . 内置的非常好,你的 cooperative inheritance 确保你使用它 .

    批评最佳答案

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

    class MyException(Exception):
        pass
    

    同样,上面的问题是,为了捕获它,你可能没有准备好处理所有类型的异常,你应该只捕获你准备处理的异常) . 类似的批评下面,但另外这不是通过 super 初始化的方式,如果你访问消息属性,你将获得 DeprecationWarning

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

    这样你就可以将错误消息的dict传递给第二个参数,然后通过e.errors获取它

    它还需要传递两个参数(除了 self 之外) . 不多也不少 . 这是未来用户可能不会欣赏的有趣约束 .

    To be direct - it violates Liskov substitutability.

    我将演示这两个错误:

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

    As of 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!

    如果需要,这是获取更多数据的异常的方法:

    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(self.payload)) # Detail: Forgot upgrading from Python 1
    

    payload=None 对于使其成为可选择的重要 . 在转储它之前,你必须调用 error.__reduce()__ . 加载将按预期工作 .

    如果需要将大量数据传输到某个外部结构,您可能应该使用pythons return 语句查找解决方案 . 对我来说,这似乎更清晰/更pythonic . 高级异常在Java中被大量使用,当使用框架并且必须捕获所有可能的错误时,这有时会令人讨厌 .

  • 15

    试试这个例子

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

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

    为应用程序创建自定义异常类时,其中许多不仅仅是从Exception继承,而是从其他类(如ValueError或类似的)继承 . 然后你必须适应他们对变量的使用 .

    如果你的应用程序中有很多例外,那么为所有这些例子设置一个通用的自定义基类通常是个好主意,这样你的模块用户就可以做了

    try:
        ...
    except NelsonsExceptions:
        ...
    

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

    在任何情况下,如果您执行与Exception本身不同的操作,则只需要 __init__ or __str__ . 因为如果弃用,则需要两者,否则会出错 . 这并不是每个课程所需的额外代码 . ;)

相关问题