Правильный способ объявления пользовательских исключений в современном Python?

как правильно объявлять пользовательские классы исключений в современном Python? Моя основная цель-следовать любым стандартным другим классам исключений, так что (например) любая дополнительная строка, которую я включаю в исключение, распечатывается любым инструментом, поймавшим исключение.

под "современным Python" я имею в виду то, что будет работать в Python 2.5, но будет "правильным" для Python 2.6 и Python 3.* способ делать вещи. И под "custom" я подразумеваю объект исключения, который может включите дополнительные данные о причине ошибки: строку, возможно, также какой-то другой произвольный объект, относящийся к исключению.

меня споткнулось следующее предупреждение об устаревании в 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, но я никогда не знал, как использовать его. Я также не уверен, что это правильный способ сделать что-то в будущем; многие обсуждения, которые я нашел в интернете, предполагали, что они пытаются покончить с args в Python 3.

обновление: два ответа предложили переопределение __init__ и __str__/__unicode__/__repr__. Это похоже на много машинописи, это необходимо?

7 ответов


может быть, я пропустил вопрос, но почему бы и нет:

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 сообщений об ошибках на второй param, и добраться до него позже с 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

С современными исключениями 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 as описано в учебник.

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

"правильный способ объявления пользовательских исключений в современном Python?"

это нормально, если ваше исключение не действительно вид более конкретное исключение:

class MyException(Exception):
    pass

или лучше (может быть, идеально), а не pass дайте docstring:

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

Подклассы Подклассы Исключения

С docs

Exception

все встроенные, не-система-выходя исключения являются производными от этого класса. Все пользовательские исключения также должны быть получены из этого класс.

что это значит если ваше исключение-это тип более конкретного исключения, подкласс этого исключения вместо универсального Exception (и результатом будет то, что вы все еще производны от Exception как врачи рекомендуют). Кроме того, вы можете по крайней мере предоставить docstring (и не быть вынуждены использовать 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

опять же, проблема с изложенным представляется, что для того, чтобы поймать его, вы должны либо конкретно (импортируя его, если будет создана в другом месте) или поймать исключение, (но ты, видимо, не готовы обрабатывать все типы исключений, и вы должны только ловить исключения, вы готовы заниматься). Аналогичная критика ниже, но, кроме того, это не способ инициализации через super, и DeprecationWarning если вы получаете доступ к атрибуту сообщение:

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 сообщений об ошибках на второй param, и добраться до него позже с e.ошибки

это также требует, чтобы было передано ровно два аргумента (помимо 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'

посмотреть, как исключения работают по умолчанию, если один vs используются дополнительные атрибуты (tracebacks опущены):

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

вы должны переопределить __repr__ или __unicode__ методы вместо использования сообщения, args, которые вы предоставляете при создании исключения, будут в args атрибут объекта исключения.


нет, "сообщение" не запрещено. Это просто устарело. Приложение будет работать с помощью сообщений. Но вы можете избавиться от ошибки осуждение, конечно.

когда вы создаете пользовательские классы исключений для своего приложения, Многие из них не подклассы только из исключения, но и из других, таких как ValueError или аналогичные. Тогда вам придется адаптироваться к их использованию переменных.

и если у вас есть много исключений в вашем приложении, это обычно хороший идея иметь общий пользовательский базовый класс для всех них, чтобы пользователи ваших модулей могли делать

try:
    ...
except NelsonsExceptions:
    ...

и в этом случае вы можете сделать __init__ and __str__ необходимо там, поэтому вам не нужно повторять его для каждого исключения. Но просто вызов переменной сообщения что-то другое, чем сообщение, делает трюк.

В любом случае, вам нужны только __init__ or __str__ Если вы делаете что-то отличное от того, что делает само исключение. И потому, что если осуждение, вам тогда нужно и то, и другое, или вы получите ошибка. Это не так много дополнительного кода, который вам нужен для каждого класса. ;)


попробуйте этот пример

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