Правильный способ объявить пользовательские исключения в современном Python? - PullRequest
1094 голосов
/ 24 августа 2009

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

Ответы [ 9 ]

1135 голосов
/ 24 августа 2009

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

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
432 голосов
/ 22 апреля 2012

С современными исключениями 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
174 голосов
/ 15 ноября 2014

«Правильный способ объявления пользовательских исключений в современном 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__. Старайтесь не передавать дикт в качестве позиционного аргумента, будущие пользователи вашего кода будут вам благодарны. Если вы используете устаревший атрибут сообщения, назначение его самостоятельно позволит избежать 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'
45 голосов
/ 07 августа 2013

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

>>> 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
15 голосов
/ 24 августа 2009

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

11 голосов
/ 25 ноября 2018

Начиная с 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, что иногда может раздражать при использовании инфраструктуры и необходимости отлавливать все возможные ошибки.

7 голосов
/ 24 августа 2009

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

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

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

try:
    ...
except NelsonsExceptions:
    ...

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

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

2 голосов
/ 22 июля 2018

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

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")
0 голосов
/ 10 июня 2019

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...