Правильный способ объявления пользовательских исключений в современном Python?
Как правильно объявлять пользовательские классы исключений в современном 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__
. Кажется, что слишком много ввода, это необходимо?
Переведено автоматически
Ответ 1
Возможно, я пропустил вопрос, но почему бы и нет:
class MyException(Exception):
pass
Чтобы переопределить что-либо (или передать дополнительные аргументы), сделайте это:
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
Таким образом, вы могли бы передать dict сообщений об ошибках во второй параметр и перейти к нему позже с помощью e.errors
.
В Python 2 вам придется использовать эту немного более сложную форму super()
:
super(ValidationError, self).__init__(message)
Ответ 2
С исключениями современного 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
Ответ 3
"Как правильно объявлять пользовательские исключения в современном Python?"
Это нормально, если только ваше исключение на самом деле не является типом более конкретного исключения:
class MyException(Exception):
pass
Или лучше (возможно, идеально), вместо pass
give a docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
Создание подклассов исключений
Из документов
Exception
Все встроенные исключения, не выходящие из системы, являются производными от этого класса. Все пользовательские исключения также должны быть производными от этого класса.
Это означает, что если ваше исключение относится к типу более конкретного исключения, создайте подкласс этого исключения вместо универсального Exception
(и результатом будет то, что вы по-прежнему производны от Exception
, как рекомендуют документы). Кроме того, вы можете, по крайней мере, предоставить docstring (и не быть вынужденным использовать pass
ключевое слово):
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
Устанавливайте атрибуты, которые вы создаете сами, с помощью пользовательского __init__
. Избегайте передачи dict в качестве позиционного аргумента, будущие пользователи вашего кода будут вам благодарны. Если вы используете атрибут deprecated 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__
or __repr__
. Встроенные исключения очень хороши, и ваше совместное наследование гарантирует, что вы их используете.
Критика главного ответа
Возможно, я пропустил вопрос, но почему бы и нет:
class MyException(Exception):
pass
Опять же, проблема с вышесказанным заключается в том, что для его перехвата вам придется либо дать ему конкретное имя (импортируя его, если оно создано в другом месте), либо перехватить Exception (но вы, вероятно, не готовы обрабатывать все типы исключений, и вам следует перехватывать только те исключения, которые вы готовы обрабатывать). Критика аналогична приведенной ниже, но, кроме того, это не способ инициализации через super
, и вы получите DeprecationWarning
если получите доступ к атрибуту message:
Редактировать: чтобы переопределить что-то (или передать дополнительные аргументы), сделайте это:
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'
Ответ 4
Чтобы правильно определять свои собственные исключения, есть несколько рекомендаций, которым вы должны следовать:
Определите базовый класс, наследуемый от
Exception
. Это позволит легко перехватывать любые исключения, связанные с проектом:class MyProjectError(Exception):
"""A base class for MyProject exceptions."""Организация классов исключений в отдельном модуле (например,
exceptions.py
), как правило, является хорошей идеей.Чтобы создать конкретное исключение, создайте подкласс базового класса exception.
class CustomError(MyProjectError):
"""A custom exception class for MyProject."""Вы также можете создавать подклассы пользовательских классов исключений для создания иерархии.
Чтобы добавить поддержку дополнительных аргументов к пользовательскому исключению, определите
__init__()
метод с переменным количеством аргументов. Вызовите базовый класс__init__()
, передавая ему любые позиционные аргументы (помните, чтоBaseException
/Exception
ожидайте любое количество позиционных аргументов). Сохраняйте дополнительные аргументы ключевого слова для экземпляра, например:class CustomError(MyProjectError):
def __init__(self, *args, **kwargs):
super().__init__(*args)
self.custom_kwarg = kwargs.get('custom_kwarg')Пример использования:
try:
raise CustomError('Something bad happened', custom_kwarg='value')
except CustomError as exc:
print(f'Сaught CustomError exception with custom_kwarg={exc.custom_kwarg}')
Этот дизайн соответствует принципу подстановки Лискова, поскольку вы можете заменить экземпляр базового класса исключений экземпляром производного класса исключений. Кроме того, это позволяет вам создать экземпляр производного класса с теми же параметрами, что и родительский.