代码之家  ›  专栏  ›  技术社区  ›  Nelson

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

  •  1036
  • Nelson  · 技术社区  · 17 年前

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

    “现代的python”是指在python 2.5中运行,但对python 2.6和python 3*来说是“正确的”。在“自定义”中,我指的是一个异常对象,它可以包含关于错误原因的额外数据:一个字符串,也可能是与异常相关的其他任意对象。

    在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 回复  |  直到 7 年前
        1
  •  1070
  •   gahooa    8 年前

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

    class MyException(Exception):
        pass
    

    编辑: 要覆盖某些内容(或传递额外的参数),请执行以下操作:

    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更新: 在python 3+中,您可以使用稍微紧凑的 super() :

    class ValidationError(Exception):
        def __init__(self, message, errors):
    
            # Call the base class constructor with the parameters it needs
            super().__init__(message)
    
            # Now for your custom code...
            self.errors = errors
    
        2
  •  411
  •   frnknstn    8 年前

    除了现代的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"])
    

    仍然可以将多个项传递给异常并通过元组索引访问它们,但这是 非常沮丧 (甚至有一段时间还打算贬值)。如果您确实需要一条以上的信息,并且上述方法不足以满足您的需要,那么您应该子类 Exception 如中所述 tutorial .

    class MyError(Exception):
        def __init__(self, message, animal):
            self.message = message
            self.animal = animal
        def __str__(self):
            return self.message
    
        3
  •  169
  •   Aaron Hall    9 年前

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

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

    class MyException(Exception):
        pass
    

    或者更好(也许完美),而不是 pass 给出docstring:

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

    子类化异常子类

    从 docs

    Exception

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

    那意味着 如果 您的异常是一个更具体的异常类型,将该异常子类化,而不是泛型 例外 (结果是你仍然从 例外 如医生建议的那样)。此外,您至少可以提供一个docstring(而不是强制使用 通过 关键词:

    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__ .内置的非常好,您的 合作继承 确保您使用它。

    对最高答案的批评

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

    类MyException(异常):
    通过
    

    同样,上面的问题是,为了捕获它,您要么必须指定它的名称(如果在其他地方创建,则导入它),要么捕获异常(但您可能不准备处理所有类型的异常,您应该只捕获准备处理的异常)。类似于下面的批评,但这不是初始化via的方法 super 你会得到一个 折旧警告 如果访问消息属性:

    编辑:要覆盖某些内容(或传递额外的参数),请执行以下操作:

    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 不多不少。这是一个有趣的限制,未来的用户可能不会欣赏。

    直截了当——这违反了 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'
    
        4
  •  42
  •   mykhal    13 年前

    查看异常在默认情况下的工作方式 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
    
        5
  •  16
  •   bignose    9 年前

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

        6
  •  6
  •   Lennart Regebro    17 年前

    不,不禁止“留言”。只是被否决了。您的应用程序可以很好地使用消息。当然,你可能会想摆脱折旧错误。

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

    如果您的应用程序中有许多异常,通常最好为所有这些异常都提供一个通用的自定义基类,这样模块的用户就可以

    try:
        ...
    except NelsonsExceptions:
        ...
    

    在这种情况下,你可以 __init__ and __str__ 需要在那里,所以您不必为每个异常重复它。但是,仅仅调用消息变量,而不是消息,就可以做到这一点。

    无论如何,你只需要 __init__ or __str__ 如果您所做的事情与异常本身所做的不同。因为如果不赞成,你就需要两者,否则你会得到一个错误。每个类不需要太多额外的代码。;)

        7
  •  5
  •   fameman    7 年前

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

    payload=None 重要的是要使它能够腌制。在倾倒之前,你必须打电话 error.__reduce()__ . 加载将按预期工作。

    你也许应该研究用蟒蛇找到解决办法。 return 语句,如果需要将大量数据传输到某个外部结构。这对我来说似乎更清楚了。高级异常在Java中被大量使用,当使用框架并必须捕捉所有可能的错误时,有时会令人恼火。

        8
  •  1
  •   omkaartg    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")
    
    推荐文章