среда, 14 марта 2012 г.

PEP 409: подавление контекста исключений

Суть
Одна из открытых проблем со времён PEP 3134 — подавление контекста, так как на тот момент решения не было найдено. Данный PEP исправляет это упущение.
Причина
Есть два способа создать исключение:

  1. Python сам делает это (ошибки кода, отсутствующие ресурсы, завершение циклов и т.д.)
  2. ручками (с помощью выражения raise)
В процессе написания библиотек или пользовательских классов часто необходимо возбуждать исключения; более того, часто полезно, или даже нужно, заменить одно исключение на другое. Вот пример из модуля dbf автора:
try:
    value = int(value)
except Exception:
    raise DbfError(...)
По любому начальное исключение (ValueError, TypeError или что-то в этом роде) уже не релевантно. С этого момента нам нужно только DbfError, однако, если это исключение будет выведено, то мы увидим информацию об обоих исключениях.
Альтернативы
Были предложены следующие возможности решения:

  • raise as NewException()
    Используется ключевое слово as. Может ввести в заблуждение, так как на самом деле мы не перевозбуждаем начальное исключение
  • raise NewException() from None
    В русле текущего синтаксиса объявления предыдущего исключения
  • exc=NewException(); exc.__context__=None; raise exc
    Более многословный способ предыдущего варианта
  • raise NewException.no_context(...)
    Создаем метод класса для подавления контекста
Предложение
Я предлагаю использовать второй вариант:
raise NewException from None
В данном случае мы имеем преимущество в том, что используем существующий шаблон для установки причины возбуждения исключения:
raise KeyError() from NameError()
но поскольку причиной будет None, предыдущий контекст не будет отображаться стандартной процедурой отображения исключения.
Обсуждение реализации
На данный момент None является  значением по умолчанию для __context__ и __cause__. Для обеспечения поддержки raise ... from None (что должно установить значение __cause__ в None) нам нужно другое значение по умолчанию для __cause__. Было предложено несколько идей, как это реализовать на уроне языка:

  • Переписать информацию в имеющемся исключении (пошагово и оставить значение None для  __cause__)
    Отклонено, так как это может серьезно помешать отладке из-за плохого сообщения об ошибке
  • Использовать одно из булевых значений в  __cause__: False может быть значением по умолчанию и при использовании from ... будет заменено либо на соответствующее исключение, либо на None
    Отклонено, так как поощряет использование двух разных типов объектов для  __cause__, причем для одного из этих типов (булева) не возможно использовать полный спектр значений (True не будет использовано)
  • Создать специальный класс исключений __NoException__
    Отклонено, так как возможно будет вводить в заблуждение или будет по ошибке возбуждено пользователями и не является уникальным значением, как None, False и True.
  • Использовать Ellipsis (многоточие) для значения по умолчанию
    Принято.
Многоточие в английском используются там, где опущены какие-либо слова. Так что в нашем случае это будет означать: "__cause__ опущено, так что смотрите в __context__ для получения более подробной информации".
Ellipsis  так же не является исключением, так что не может быть возбуждено.
Есть только Ellipsis, так что не будет неиспользованных значений
Информация об ошибке не выбрасывается, то что пользовательский код может провести трассировку цепи исключений при том, что по умолчанию это не происходит.
Детали языка
Для поддержки raise Exception from None, __context__ останется каким и был, а  __cause__  получит значение по умолчанию Ellipsis и будет заменено на None при использовании выражения  Exception from None.
форма __context__ __cause__
raise None Ellipsis
повторное raise предыдущее исключение Ellipsis
повторное raise from None | ChainedException предыдущее исключение None | цепь исключений
Процедура вывода исключения по умолчанию будет работать так:

  • если  __cause__ равно Ellipsis, __context__ (если есть) будет выведен
  • если  __cause__  равно None, __context__ не будет выведен
  • если  __cause__  содержит любое другое значение, будет отображено  __cause__  
В двух последних случаях следование по цепи исключений будет прекращено.
Поскольку значение по умолчанию для __cause__ теперь Ellipsis, a raise Exception form Cause лишь более короткая форма для:
_exc = NewException()
_exc.__cause__ = Cause()
raise _exc
Ellipsis, как и None, теперь может быть "причиной":
raise Exception from Ellipsis

Комментариев нет:

Отправить комментарий