Правильный способ объявления пользовательских исключений в современном Python?
Каков правильный способ объявления пользовательских классов исключений в современном 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__
. Это похоже на много типизирования, нужно ли это?
Ответы
Ответ 1
Может быть, я пропустил вопрос, но почему бы и нет
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
Ответ 2
С современными исключениями 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
как описано в руководстве.
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
введите 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
Опять же, проблема с вышесказанным заключается в том, что для того, чтобы поймать его, вам придется либо называть его конкретным (импортировать его, если он создан в другом месте), либо catch 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
Таким образом, вы можете передать dict сообщений об ошибках второму параметру и получить его позже с помощью e.errors
Также требуется передать только два аргумента (кроме self
.) Нет больше, не меньше. Это интересное ограничение, которое будущие пользователи могут не оценить.
Чтобы быть прямым - это нарушает Liskov substitutability.
Я продемонстрирую обе ошибки:
>>> 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
посмотреть, как исключения работают по умолчанию, если используется один или несколько атрибутов (трассировки опущены):
>>> 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
Ответ 5
Вы должны переопределить методы __repr__
или __unicode__
вместо использования сообщения, аргументы, которые вы предоставляете при построении исключения, будут в атрибуте args
объекта исключения.
Ответ 6
Начиная с 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__()
. Загрузка будет работать как положено.
Возможно, вам следует поискать решение с помощью оператора return
pythons, если вам нужно перенести много данных в какую-либо внешнюю структуру. Это кажется более ясным/более питоническим для меня. Расширенные исключения интенсивно используются в Java, что иногда может раздражать при использовании инфраструктуры и необходимости отлавливать все возможные ошибки.
Ответ 7
Нет, "сообщение" не запрещено. Это просто не рекомендуется. Приложение будет работать с использованием сообщения. Но, конечно, вы можете избавиться от ошибки устаревания.
Когда вы создаете пользовательские классы исключений для своего приложения, многие из них не подклассы только от Exception, а от других, например ValueError или аналогичные. Затем вам нужно адаптироваться к использованию переменных.
И если в вашем приложении есть много исключений, обычно полезно иметь общий настраиваемый базовый класс для всех из них, чтобы пользователи ваших модулей могли делать
try:
...
except NelsonsExceptions:
...
И в этом случае вы можете сделать __init__ and __str__
, необходимый там, поэтому вам не нужно повторять его для каждого исключения. Но просто вызывать переменную сообщения чем-то еще, чем сообщение, делает трюк.
В любом случае вам нужен только __init__ or __str__
, если вы делаете что-то отличное от того, что делает Исключение. И потому что, если вы устали, вам тогда нужны оба, или вы получите сообщение об ошибке. Это не целый дополнительный код, который вам нужен для каждого класса.;)
Ответ 8
Попробуйте этот пример
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")
Ответ 9
Смотрите очень хорошую статью " Полное руководство по исключениям Python ". Основные принципы:
- Всегда наследовать от (по крайней мере) исключения.
- Всегда вызывайте
BaseException.__init__
только с одним аргументом. - При создании библиотеки определите базовый класс, унаследованный от Exception.
- Укажите подробности об ошибке.
- Наследовать от встроенных типов исключений, когда это имеет смысл.
Также есть информация по организации (в модулях) и упаковке исключений, я рекомендую прочитать руководство.