Как правильно объявлять пользовательские классы исключений в современном 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__. Это похоже на много печатать, это необходимо?

1505
Nelson 24 Авг 2009 в 01:29

9 ответов

Лучший ответ

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

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

Таким образом, вы можете передать сообщение об ошибке второму параметру и перейти к нему позже с помощью 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
1251
gahooa 26 Мар 2018 в 17:08

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

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")
3
Omkaar.K 22 Июл 2018 в 05:27

Смотрите очень хорошую статью «Полное руководство по исключениям Python». Основные принципы:

  • Всегда наследовать от (по крайней мере) исключения.
  • Всегда вызывайте BaseException.__init__ только с одним аргументом.
  • При создании библиотеки определите базовый класс, наследуемый от Exception.
  • Укажите подробности об ошибке.
  • Наследовать от встроенных типов исключений, когда это имеет смысл.

Также есть информация по организации (в модулях) и упаковке исключений, я рекомендую прочитать руководство.

6
Yaroslav Nikitenko 10 Июн 2019 в 20:40

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

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

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

try:
    ...
except NelsonsExceptions:
    ...

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

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

8
Lennart Regebro 23 Авг 2009 в 21:58

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

18
bignose 18 Авг 2017 в 05:11

Начиная с 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 важно, чтобы сделать его засоленным. Прежде чем выбросить его, вы должны позвонить error.__reduce__(). Загрузка будет работать как положено.

Возможно, вам следует поискать решение, используя оператор pythons return, если вам нужно перенести много данных в какую-либо внешнюю структуру. Это кажется более ясным / более питоническим для меня. Расширенные исключения интенсивно используются в Java, что иногда может раздражать при использовании инфраструктуры и необходимости отлавливать все возможные ошибки.

28
fameman 28 Апр 2019 в 21:03

Посмотрите, как исключения работают по умолчанию, если используется один 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
49
mykhal 7 Авг 2013 в 16:23

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

Это нормально, если ваше исключение не является типом более конкретного исключения:

class MyException(Exception):
    pass

Или лучше (может быть идеально) вместо pass дать строку документации:

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

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

Из документов.

Exception

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

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

Критика топ-ответа

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

class MyException(Exception):
    pass

Опять же, проблема с вышеприведенным заключается в том, что для того, чтобы поймать его, вам нужно либо указать его конкретно (импортировать, если он создан в другом месте), либо перехватить Exception, (но вы, вероятно, не готовы обрабатывать все типы исключений, и вы должны ловить только исключения, которые вы готовы обработать). Критика аналогична приведенной ниже, но, кроме того, это не способ инициализации через super, и вы получите 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'
192
Aaron Hall 23 Фев 2017 в 19:04

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

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message
472
frnknstn 10 Май 2018 в 08:02