Как я могу вставить ловимое исключение в Python из C с C Python API? - PullRequest
2 голосов
/ 11 марта 2019

Я выполняю следующую функцию Python из C:

Python:

def runLoop(self):
    try:
        while True:
            print("working")
            time.sleep(1)  # Delay for 1 second.
            self.incrementcounter()  
    except:
        print("caught exception")

C:

//Here we register traceFunction to give us a hook into the python 
PyEval_SetProfile((Py_tracefunc)traceFunction, NULL);

//Abort interrupt reset to false
m_interruptRequest = false;

//Call the runLoop method
const char *szName = "runLoop";

PyObject_CallMethod(m_pyObject, szName, nullptr);

В середине этого цикла я бынравится вставлять исключение из C, чтобы прервать цикл, но также обрабатывать исключение в try / кроме Python.Как уже упоминалось в комментариях, я регистрирую функцию профилирования в C, которая называется tracer и может выдавать исключение из C. Следующий код ниже вводит исключение и убивает программу, потому что она не перехватывается при попытке / исключении.

// Функция обратного вызова функции Tracer подключается к выполнению Python.Здесь мы обрабатываем запросы на паузу, возобновление и прерывание.

void PythonRunner::tracer(void)
{
    ...
    //Abort requested
    else if (true == interruptRequested())
    {
        PyErr_SetString(PyExc_Exception, "ABORT");


    }
}

Кто-нибудь знает, как я могу выбросить исключение из C для обработки в Python?Спасибо

Обновление: с помощью @zwol и этой статьи stackoverflow.com/questions/1420957/stopping-embedded-python я нашел решение.Ключом было добавить ожидающий вызов в Python, например

    int PythonRunner::tracer(PyObject *, _frame *, int, PyObject *)
    {
        ...
        //Abort requested
        else if (true == _instance->interruptRequested())
        {
            Py_AddPendingCall(&PythonRunner::raiseException, NULL);
        }
    return 0;
   }

int PythonRunner::raiseException(void *)
{
    PyErr_SetString(PyExc_KeyboardInterrupt, "Abort");
    return -1;
}

1 Ответ

2 голосов
/ 11 марта 2019

В общем, чтобы вызвать исключение с помощью CPython C API, вы вызываете одну из PyErr_Set* функций , а затем возвращаете специальное значение из вашей функции API . Для функций, которые обычно возвращают объект Python, это специальное значение равно NULL; для других функций вы должны проверить документацию, которая может существовать только в заголовочных файлах.

В этом случае цитируем pystate.h (из CPython 3.7):

/* Py_tracefunc return -1 when raising an exception, or 0 for success. */
typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *);

Это означает, что вы должны объявить PythonRunner::tracer с соответствующей подписью, например:

class PythonRunner {
    // ...
public:
    static int tracer(PyObject *, struct _frame *, int, PyObject *);
}

И затем вы можете вернуть & минус; 1, чтобы сообщить об исключении, или 0, чтобы продолжить нормально:

int PythonRunner::tracer(PyObject *, struct _frame *, int, PyObject *)
{
    ...
    // Abort requested?
    else if (interruptRequested())
    {
        PyErr_SetString(PyExc_Exception, "ABORT");
        return -1;
    }
    // Otherwise proceed normally.
    return 0;
}

Кроме того, ваш вызов PyEval_SetProfile больше не будет нуждаться в приведении.

PyEval_SetProfile(PythonRunner::tracer, NULL);

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

(По историческим причинам пример кода в документации Python «Расширение и встраивание» содержит кучу ненужных приведений и функций с неправильными подписями. Всякий раз, когда вы копируете код из этого документа, удаляйте все приведенные приведения, а затем тщательно Изучите жалобы компилятора, настройте сигнатуры типов и верните только те составы, которые неизбежны.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...