Как правильно объявлять пользовательские классы исключений в современном 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__
. Это похоже на много печатать, это необходимо?
источник
В современных исключений Python, вам не нужно злоупотреблять
.message
, или переопределить.__str__()
или.__repr__()
или какой - либо из него. Если все, что вам нужно, это информативное сообщение при возникновении вашего исключения, сделайте это:Это даст обратную трассировку, заканчивающуюся на
MyException: My hovercraft is full of eels
.Если вы хотите большей гибкости от исключения, вы можете передать словарь в качестве аргумента:
Однако разобраться в этих деталях в
except
блоке немного сложнее. Детали хранятся вargs
атрибуте, который является списком. Вам нужно будет сделать что-то вроде этого:Все еще возможно передать несколько элементов в исключение и получить к ним доступ через индексы кортежей, но это крайне нежелательно (и даже было предназначено для устаревания некоторое время назад). Если вам нужно больше, чем один фрагмент информации, и вышеописанный метод вам не подходит, тогда вы должны создать подкласс,
Exception
как описано в руководстве .источник
Exception(foo, bar, qux)
.Это нормально, если только ваше исключение не является типом более конкретного исключения:
Или лучше (может быть идеально), вместо того, чтобы
pass
дать строку документации:Подклассы Исключение Подклассы
Из документов
Это означает, что если ваше исключение является типом более конкретного исключения, создайте подкласс этого исключения вместо универсального
Exception
(и в результате вы все равно получите его,Exception
как рекомендуют документы). Кроме того, вы можете по крайней мере предоставить строку документации (и не быть вынужденным использоватьpass
ключевое слово):Установите атрибуты, которые вы создаете сами по себе
__init__
. Старайтесь не передавать дикт в качестве позиционного аргумента, будущие пользователи вашего кода будут вам благодарны. Если вы используете устаревший атрибут сообщения, назначение его самостоятельно позволит избежатьDeprecationWarning
:Там действительно нет необходимости писать свой
__str__
или__repr__
. Встроенные очень хороши, и ваше совместное наследование гарантирует, что вы используете его.Критика топ-ответа
Опять же, проблема с вышеприведенным заключается в том, что для того, чтобы поймать его, вам нужно будет либо указать его конкретно (импортировать, если он создан в другом месте), либо перехватить Exception, (но вы, вероятно, не готовы обрабатывать все типы исключений, и вы должны ловить только исключения, которые вы готовы обработать). Критика аналогична приведенной ниже, но, кроме того, это не способ инициализации через
super
, и вы получите,DeprecationWarning
если получите доступ к атрибуту сообщения:Это также требует, чтобы было передано ровно два аргумента (кроме
self
.) Не больше, не меньше. Это интересное ограничение, которое будущие пользователи могут не оценить.Быть прямым - нарушает подставляемость Лискова .
Я продемонстрирую обе ошибки:
По сравнению с:
источник
BaseException.message
отсутствует в Python 3, поэтому критика верна только для старых версий, верно?__str__
методMyAppValueError
вместо того, чтобы полагаться наmessage
атрибутValueError
. Это имеет смысл, если оно относится к категории ошибочных значений. Если это не относится к категории ошибок значений, я бы поспорил против семантики. Программисту есть место для некоторых нюансов и рассуждений, но я предпочитаю конкретность, когда это применимо. Я обновлю свой ответ, чтобы лучше заняться этим вопросом в ближайшее время.Посмотрите, как исключения работают по умолчанию, если используется один или несколько атрибутов (обратные ссылки пропущены):
поэтому вы можете захотеть иметь своего рода « шаблон исключения », работающий как само исключение совместимым способом:
это можно легко сделать с помощью этого подкласса
и если вам не нравится это представление, похожее на кортеж по умолчанию, просто добавьте
__str__
метод вExceptionTemplate
класс, например:и у вас будет
источник
Начиная с Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), рекомендуемый метод по-прежнему:
Пожалуйста, не забудьте документировать, почему пользовательское исключение необходимо!
Если вам нужно, это способ использовать исключения с большим количеством данных:
и получить их как:
payload=None
важно сделать его маринованным. Прежде чем сбросить его, вы должны позвонитьerror.__reduce__()
. Загрузка будет работать как положено.Возможно, вам следует изучить вопрос о поиске решения с помощью
return
оператора pythons, если вам нужно перенести много данных в какую-либо внешнюю структуру. Это кажется более ясным / более питоническим для меня. Расширенные исключения интенсивно используются в Java, что иногда может раздражать при использовании инфраструктуры и необходимости отлавливать все возможные ошибки.источник
__str__
), а не другие ответы, которые используютsuper().__init__(...)
.. Просто позор, который переопределяет__str__
и__repr__
, вероятно, необходим только для лучшей сериализации «по умолчанию».Вы должны переопределить
__repr__
или__unicode__
методы вместо использования сообщения, аргументы, которые вы предоставляете при создании исключения, будут вargs
атрибуте объекта исключения.источник
Нет, «сообщение» не запрещено. Это просто устарело. Ваше приложение будет нормально работать с использованием сообщений. Но вы можете избавиться от ошибки устаревания, конечно.
Когда вы создаете пользовательские классы Exception для своего приложения, многие из них не делятся на подклассы только из Exception, а из других, например ValueError или аналогичных. Затем вы должны адаптироваться к их использованию переменных.
И если у вас есть много исключений в вашем приложении, обычно хорошей идеей является наличие общего пользовательского базового класса для всех них, чтобы пользователи ваших модулей могли делать
И в этом случае вы можете сделать
__init__ and __str__
необходимое там, так что вам не нужно повторять это для каждого исключения. Но простой вызов переменной message, отличной от message, делает свое дело.В любом случае, вам нужно только
__init__ or __str__
если вы делаете что-то отличное от того, что делает само Exception. А потому, что если начисляется амортизация, то вам нужны оба, иначе вы получите ошибку. Это не много дополнительного кода, который вам нужен для каждого класса. ;)источник
Смотрите очень хорошую статью " Полное руководство по исключениям Python ". Основные принципы:
BaseException.__init__
только с одним аргументом.Также есть информация по организации (в модулях) и упаковке исключений, я рекомендую прочитать руководство.
источник
Always call BaseException.__init__ with only one argument.
Похоже на ненужное ограничение, так как фактически принимает любое количество аргументов.Попробуйте этот пример
источник
Чтобы правильно определить ваши собственные исключения, есть несколько рекомендаций, которым вы должны следовать:
Определите базовый класс, наследующий от
Exception
. Это позволит отлавливать любые исключения, связанные с проектом (более конкретные исключения должны наследовать от него):Организация этих классов исключений в отдельном модуле (например
exceptions.py
), как правило, хорошая идея.Чтобы создать пользовательское исключение, создайте подкласс базового класса исключения.
Чтобы добавить поддержку дополнительных аргументов в пользовательское исключение, определите пользовательский
__init__()
метод с переменным числом аргументов. Вызовите базовый класс__init__()
, передав ему любые позиционные аргументы (помните, чтоBaseException
/Exception
ожидайте любое количество позиционных аргументов ):Чтобы вызвать такое исключение с дополнительным аргументом, вы можете использовать:
Этот дизайн придерживается принципа подстановки Лискова , так как вы можете заменить экземпляр базового класса исключений на экземпляр производного класса исключений. Кроме того, он позволяет создавать экземпляр производного класса с теми же параметрами, что и родительский.
источник