Приложение C++ падает, когда встроенный интерпретатор Python пытается импортировать внешний модуль во второй раз

Если я дважды импортирую внешний модуль в разных сеансах pybind11::scoped_interpreter, приложение аварийно завершает работу в файле eval.h в функции eval в следующей строке:

PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr());

с

Exception thrown at 0x00007FFD710C4E0C (multiarray.cp36-win_amd64.pyd) in pybind-test.exe: 0xC0000005: Access violation writing location 0x000000000000000A.

Воспроизводимый пример кода

namespace py = pybind11;
void test() {
    try {
        py::scoped_interpreter guard{};
        py::object mainScope = py::module::import("__main__").attr("__dict__");
        py::exec(
            "import numpy\n",
            mainScope);
    }
    catch (py::error_already_set const &pythonErr) {  std::cout << pythonErr.what(); }
}
int main() {
    test();   // Runs fine
    test();   // Crashes at py::exec
}

Я чувствую, что это связано с комментарием в embed.h pybind11:

Переводчик может быть перезапущен по телефону initialize_interpreter снова. Модули, созданные с помощью pybind11, можно безопасно переинициализировать. Однако сам Python не может полностью выгрузить двоичные модули расширения, и есть несколько предостережений относительно перезапуска интерпретатора. Все подробности можно найти в документации CPython. Короче говоря, не вся память интерпретатора может быть освобождена либо из-за циклов ссылок, либо из-за созданных пользователем глобальных данных.

То есть нет способа дважды вызвать интерпретатор Python? У меня есть файл Python, содержащий вспомогательные функции NumPy, которые мне нужно вызывать в разных точках выполнения алгоритма из C++. Значит ли это, что я не могу этого сделать?

2 ответа

Решение

Перефразируя из обсуждения на pybind11 github repo.

Вместо того, чтобы использовать py::scoped_interpreter использование py::initialize_interpreter а также py::finalize_interpreter, Звоните переводчику столько раз, сколько хотите.

Предостережение: "Интерпретатор Python не является полностью поточно-ориентированным. Для поддержки многопоточных программ на Python существует глобальная блокировка, называемая глобальной блокировкой интерпретатора или GIL".

Пример использования:

namespace py = pybind11;
void test() {
    try {
        py::object mainScope = py::module::import("__main__").attr("__dict__");
        py::exec(
            "import numpy\n",
            mainScope);
    }
    catch (py::error_already_set const &pythonErr) {  std::cout << pythonErr.what(); }
}
int main() {
   py::initialize_interpreter();
    test();  
    test();   
    py::finalize_interpreter();
}

В настоящее время может быть лучше никогда не вызывать PyFinalize ни напрямую, ни через scoped_interpreter, согласно этой статье .

Ошибки могут возникнуть при повторной загрузке модулей после вызова finalize и повторной инициализации. Я столкнулся с этим, следуя текущему утвержденному ответу /questions/1110163/prilozhenie-c-padaet-kogda-vstroennyij-interpretator-python-pyitaetsya-importirovat-vneshnij-modul-vo-vtoroj-raz/1110170#1110170.

https://www.boost.org/doc/libs/1_47_0/libs/python/todo.html#pyfinalize-safetyhttps://www.boost.org/doc/libs/1_47_0/libs/python/todo.html#pyfinalize-safety

Безопасность PyFinalize: в настоящее время Boost.Python имеет несколько глобальных (или функционально-статических) объектов, существование которых не дает счетчикам ссылок обнуляться до тех пор, пока общий объект Boost.Python не будет выгружен. Это может вызвать сбой, потому что, когда счетчики ссылок действительно обнуляются, интерпретатора нет. Чтобы сделать вызов PyFinalize() безопасным, мы должны зарегистрировать процедуру atexit, которая уничтожает эти объекты и освобождает все счетчики ссылок Python, чтобы Python мог очистить их, пока еще есть интерпретатор. Дирк Герритс пообещал выполнить эту работу.

Другие вопросы по тегам