Если ваша очистка - всего лишь куча Py_DECREF
, вам не нужно звонить PyErr_Fetch
.Py_DECREF
предназначен для безопасного вызова с набором исключений.Если код внутри Py_DECREF
должен делать что-то, что небезопасно делать с набором исключений, он будет нести ответственность за сохранение и восстановление состояния исключения.(Если ваша очистка включает в себя больше, чем просто Py_DECREF
, вам, возможно, придется самостоятельно что-то делать.)
Например, tp_finalize
, один из этапов уничтожения объекта, наиболее вероятный для вызова произвольного кода Python, несет полную ответственность за сохранение и восстановление активного исключения :
tp_finalize
не должно изменять текущее состояние исключения;поэтому, рекомендуемый способ написать нетривиальный финализатор:
static void
local_finalize(PyObject *self)
{
PyObject *error_type, *error_value, *error_traceback;
/* Save the current exception, if any. */
PyErr_Fetch(&error_type, &error_value, &error_traceback);
/* ... */
/* Restore the saved exception. */
PyErr_Restore(error_type, error_value, error_traceback);
}
Для __del__
методов, написанных на Python, вы можете увидеть соответствующую обработку в slot_tp_finalize
:
/* Save the current exception, if any. */
PyErr_Fetch(&error_type, &error_value, &error_traceback);
/* Execute __del__ method, if any. */
del = lookup_maybe_method(self, &PyId___del__, &unbound);
if (del != NULL) {
res = call_unbound_noarg(unbound, del, self);
if (res == NULL)
PyErr_WriteUnraisable(del);
else
Py_DECREF(res);
Py_DECREF(del);
}
/* Restore the saved exception. */
PyErr_Restore(error_type, error_value, error_traceback);
Система слабых ссылок также берет на себя ответственность за сохранение состояния исключения перед вызовом обратных вызовов со слабыми ссылками:
if (*list != NULL) {
PyWeakReference *current = *list;
Py_ssize_t count = _PyWeakref_GetWeakrefCount(current);
PyObject *err_type, *err_value, *err_tb;
PyErr_Fetch(&err_type, &err_value, &err_tb);
if (count == 1) {
PyObject *callback = current->wr_callback;
current->wr_callback = NULL;
clear_weakref(current);
if (callback != NULL) {
if (((PyObject *)current)->ob_refcnt > 0)
handle_callback(current, callback);
Py_DECREF(callback);
}
}
else {
...
Так что Py_DECREF
при вызовеустановлено исключение, это страшно, и хорошо, что вы думаете об этом, но если код уничтожения объектов работает правильно, все должно быть в порядке.
Так что, если вам придется делатьбольше очистки, чем просто очистка ваших ссылок?В этом случае, если ваша очистка небезопасна для набора исключений, вам, вероятно, следует вызвать PyErr_Fetch
и PyErr_Restore
состояние исключения, когда вы закончите.Если что-то вызывает другое исключение во время очистки, вы можете либо связать его в цепочку ( неловко, но возможно на уровне C), либо вывести короткое предупреждение в stderr с помощью PyErr_WriteUnraisable
изатем подавите новое исключение, PyErr_Clear
-ing его или PyErr_Restore
-ing исходное состояние исключения поверх него.