Изменение или ререйзинг ошибки Python в C API - PullRequest
0 голосов
/ 25 июня 2018

У меня есть немного кода, который пытается проанализировать объект как целое число:

long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
    return -1;
}

Здесь obj - это ваниль PyObject *, а PyLong_AsLong повышаеточень общий TypeError, если obj не является целым числом.

Я хотел бы преобразовать сообщение об ошибке в нечто более информативное, поэтому я хотел бы либо изменить существующий объект ошибки, либоподнять его.

Мое текущее решение состоит в том, чтобы сделать это:

long val = PyLong_AsLong(obj);
if(val == -1 && PyErr_Occurred()) {
    PyErr_Clear();
    PyErr_Format(PyExc_TypeError, "Parameter must be an integer type, but got %s", Py_TYPE(obj)->tp_name);
    return -1;
}

Это правильный способ вывести ошибку?В частности,

  1. Нужно ли вообще звонить PyErr_Clear?Я подозреваю, что он правильно расшифровывает существующий объект исключения, но я не уверен.
  2. Могу ли я изменить сообщение об ошибке, которая уже была выдана в этот момент, без повторного вызова?
  3. Есть ли возможность сделать эквивалент raise new_err from old_err?

Я не уверен, как использовать PyErr_SetExcInfo для этой ситуации, хотя моя интуиция говорит мне об этомможет иметь отношение как-то.

1 Ответ

0 голосов
/ 28 июня 2018

Ваш существующий код в порядке, но если вы хотите сделать эквивалент цепочки исключений, вы можете.Если вы хотите перейти к тому, как это сделать, перейдите к пункту 3 в конце ответа.


Чтобы объяснить, как можно выполнять такие действия, как изменение распространяющегося исключения или выполнение эквивалента raise Something() from existing_exceptionВо-первых, нам нужно объяснить, как работает состояние исключения на уровне C.

Распространяющееся исключение представлено на поток индикатор ошибки , состоящий из тип , значение и traceback .Это звучит очень похоже на sys.exc_info(), но это не то же самое.sys.exc_info() для исключений, которые были пойманы кодом уровня Python, а не для исключений, которые все еще распространяются.

Индикатор ошибки может быть ненормализован , который в основномозначает, что работа по созданию объекта исключения не была выполнена, и значение в индикаторе ошибки не является экземпляром исключения type .Это состояние существует для эффективности;если индикатор ошибки очищается на PyErr_Clear до того, как потребуется нормализация, Python пропускает большую часть работы по созданию исключения.Нормализация исключений выполняется с помощью PyErr_NormalizeException, с небольшим количеством дополнительной работы в PyException_SetTraceback для установки атрибута __traceback__ объекта исключения.

PyErr_ClearЭто что-то вроде эквивалента блока C except, но он просто очищает индикатор ошибки, не позволяя вам просматривать большую часть информации об исключениях.Чтобы поймать исключение и проверить его, вам нужно PyErr_Fetch.PyErr_Fetch похоже на перехват исключения и проверку sys.exc_info(), но оно не устанавливает sys.exc_info() и не нормализует исключение.Он очищает индикатор ошибки и выдает вам необработанное содержимое индикатора ошибки напрямую.

Явная цепочка исключений (raise Something() from existing_exception) работает, выполняя операции PyException_SetCause, чтобы установить новые исключения __cause__ к существующему исключению.Для этого требуются объекты исключений для обоих исключений, поэтому, если вы хотите сделать эквивалент из C, вам придется нормализовать исключения и вызвать PyException_SetCause самостоятельно.

Неявное сцепление исключений (raise Something() в *Блок 1060 *) работает через PyException_SetContext, чтобы установить новое исключение __context__ в существующее исключение.Подобно PyException_SetCause, для этого требуются объекты исключений и нормализация исключений.raise Something() from existing_exception внутри блока except фактически устанавливает и __cause__, и __context__, и если вы хотите выполнить явную цепочку исключений на уровне C, вы обычно должны делать то же самое.


  1. Технически, насколько я могу судить, не обязательно, но, возможно, в любом случае это хорошая идея.Похоже, PyErr_Format и другие функции, которые устанавливают индикатор ошибки, сначала очистят индикатор ошибки, если он уже установлен, но для большинства из них это не задокументировано.
  2. В некотором роде, но, вероятно, это плохоидея.Вы можете нормализовать индикатор ошибки и установить атрибут message объекта исключения, но это не повлияет на args или что-либо еще, что класс исключения может сделать со своими аргументами, и это может привести к странным проблемам.В качестве альтернативы вы можете получить индикатор ошибки с помощью PyErr_Fetch и восстановить его с новой строкой для значения с PyErr_Restore, но при этом объект существующего исключения будет отброшен, если он есть, и он делает предположения о сигнатуре класса исключения.
  3. Да, это возможно, но делать это с помощью общедоступных функций C API довольно неудобно и вручную.Вам придется вручную выполнить большую часть нормализации, поднятия и повышения исключений.

    Там есть усилий , чтобы сделать цепочку исключений уровня C более удобной, нодо сих пор все более удобные функции считаются внутренними.Например, _PyErr_FormatFromCause похож на PyErr_Format, но он объединяет новое исключение из существующего распространяющегося исключения (через __context__ и __cause__.

    Я бы не рекомендовал звонить прямо сейчас;это очень новый (3.6+), и он очень вероятно изменится (в частности, я бы не удивился, увидев, что он потеряет свое ведущее подчеркивание в новой версии Python).Вместо этого, копирование реализации из _PyErr_FormatFromCause / _PyErr_FormatVFromCause (и соблюдение лицензии ) - это хороший способ убедиться, что у вас есть правильные биты нормализации и правильной цепочки.

    Это также полезная ссылка для работы, если вы хотите выполнить неявную (__context__ только) цепочку исключений на уровне C - просто удалите часть, которая обрабатывает __cause__.

...