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




проверка на ошибку python (6)

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

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


С современными исключениями Python вам не нужно злоупотреблять .message или переопределять .__str__() или .__repr__() или любой из них. Если все, что вы хотите, является информативным сообщением, когда ваше исключение возбуждено, сделайте следующее:

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

Это даст 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

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

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

«Правильный способ объявления пользовательских исключений в современном 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'''

Установите атрибуты, которые вы создаете сами, с помощью пользовательского __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

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

Таким образом, вы можете передать dict сообщений об ошибках ко второму параметру и получить его позже с помощью 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'

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

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

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

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

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

try:
    ...
except NelsonsExceptions:
    ...

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

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


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





exception