Правильный способ объявить пользовательские исключения в современном 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__. Это похоже на много набрав, это необходимо?
7 ответов:
может быть, я пропустил вопрос, но почему бы и нет:
class MyException(Exception): passEdit: чтобы переопределить что-то (или передать дополнительные аргументы), этого:
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