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



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



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

856   7  

7 ответов:

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

class MyException(Exception):
    pass

Edit: чтобы переопределить что-то (или передать дополнительные аргументы), этого:

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 как описано в учебник.

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 дайте строку документа:

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

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

Edit: чтобы переопределить что-то (или передать дополнительные аргументы), сделайте это:

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

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

>>> 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__ методы вместо использования message, args, которые вы предоставляете при создании исключения, будут находиться в args атрибут объекта исключения.

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

при создании пользовательских классов исключений для вашего приложения Многие из них не являются подклассами только из Exception, но и из других, таких как 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")

Comments

    Ничего не найдено.