Обработка исключений в C API в основном написана с точки зрения вызова исключения его классом, его аргументами (передаваемыми в конструктор) и его трассировкой, и, следовательно, теми, которые, вероятно, лучше следовать этой схеме.Ваш базовый подход к передаче кортежа в качестве аргументов, вероятно, является наилучшим вариантом.
Однако есть два варианта сделать класс исключений немного более удобным для пользователя на стороне Python:
- Вы обрабатываете аргументы в пользовательском методе
__init__
для установки атрибута code
в классе. - Вы определяете
code
как свойство вашего класса исключений, которое обращается к args[1]
.
Я проиллюстрировал вариант 2, но я не думаю, что есть огромная причина предпочесть тот или другой.
Чтобы кратко объяснить пример кода ниже: определитьисключение при использовании C API вы используете PyErr_NewException
, который принимает необязательный базовый класс и словарь в качестве второго и третьего аргументов.Используемые функции (либо __init__
, либо определения свойств) должны быть частью словаря.
Чтобы определить определения свойств, я написал код на Python и использовал PyRun_String
, поскольку его легче писать вPython, а не C и потому что я сомневаюсь, что этот код будет критичным для производительности.Функции в конечном итоге вводятся в глобальный словарь, передаваемый в PyRun_String
.
C code:
#include <Python.h>
PyObject* make_getter_code() {
const char* code =
"def code(self):\n"
" try:\n"
" return self.args[1]\n"
" except IndexError:\n"
" return -1\n"
"code = property(code)\n"
"def message(self):\n"
" try:\n"
" return self.args[0]\n"
" except IndexError:\n"
" return ''\n"
"\n";
PyObject* d = PyDict_New();
PyDict_SetItemString(d, "__builtins__", PyEval_GetBuiltins());
PyObject* output = PyRun_String(code,Py_file_input,d,d);
if (output==NULL) {
Py_DECREF(d);
return NULL;
}
Py_DECREF(output);
PyDict_DelItemString(d,"__builtins__"); /* __builtins__ should not be an attribute of the exception */
return d;
}
static PyObject* MyLibraryError;
static PyObject* my_library_function(PyObject* self) {
/* something's gone wrong */
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyUnicode_FromString("Helpful error message"));
PyTuple_SetItem(tuple, 1, PyLong_FromLong(257));
PyErr_SetObject(MyLibraryError, tuple);
return NULL;
}
static PyMethodDef methods[] = {
{"my_library_function", my_library_function, METH_NOARGS,
"raise an error."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef librarymodule = {
PyModuleDef_HEAD_INIT,
"library", /* name of module */
NULL, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
methods
};
PyMODINIT_FUNC
PyInit_library(void) {
PyObject *m;
m = PyModule_Create(&librarymodule);
if (m == NULL)
return NULL;
PyObject* exc_dict = make_getter_code();
if (exc_dict == NULL) {
return NULL;
}
MyLibraryError = PyErr_NewException("library.MyLibraryError",
NULL, // use to pick base class
exc_dict);
PyModule_AddObject(m,"MyLibraryError",MyLibraryError);
return m;
}
В качестве примера более элегантного интерфейса Python ваш код Python изменяется на:
try:
my_library_func()
except MyLibraryError as ex:
message, code = ex.message, ex.code
Приложение: можно установить атрибуты для установленного в данный момент исключения.Базовая схема будет выглядеть так:
PyErr_SetString(...) /* raise the exception with an error message */
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback); /* gets the values of the currently
set exception and clears it */
PyObject_SetAttrString(value, "code", number);
PyErr_Restore(type,value,traceback); /* Re-sets the exception now including
the error code */
В этом случае отсутствует проверка ошибок.Я думаю, что я все еще предпочитаю свой подход к помещению логики в класс исключений, но именно так вы бы выполнили операцию, о которой вы просили.