Как скомпилировать программу, использующую Python C API
Пишу на C++ программу, которая использует Python C API, и теперь пытаюсь ее собрать, но в ответ получаю множество сообщений вида:
/usr/bin/ld: /tmp/ccJkKbND.o: в функции «Py_XDECREF»:
mathFunProvider.cpp:(.text+0xa2): неопределённая ссылка на «_Py_Dealloc»
/usr/bin/ld: /tmp/ccJkKbND.o: в функции «mathFunProvider::mathFunProvider()»:
mathFunProvider.cpp:(.text+0xca): неопределённая ссылка на «Py_Initialize»
/usr/bin/ld: /tmp/ccJkKbND.o: в функции «mathFunProvider::~mathFunProvider()»:
mathFunProvider.cpp:(.text+0x107): неопределённая ссылка на «Py_Finalize»
/usr/bin/ld: /tmp/ccJkKbND.o: в функции «mathFunProvider::vector_to_py_list(std::vector<double, std::allocator<double> >)»:
mathFunProvider.cpp:(.text+0x13f): неопределённая ссылка на «PyList_New»
/usr/bin/ld: mathFunProvider.cpp:(.text+0x1a5): неопределённая ссылка на «PyFloat_FromDouble»
/usr/bin/ld: mathFunProvider.cpp:(.text+0x1b7): неопределённая ссылка на «PyList_Append»
/usr/bin/ld: /tmp/ccJkKbND.o: в функции «mathFunProvider::load_new_fun[abi:cxx11](_object*, _object*)::{lambda(_object*, std::vector<double, std::allocator<double> >)#1}::operator()(_object*, std::vector<double, std::allocator<double> >) const»:
mathFunProvider.cpp:(.text+0x252): неопределённая ссылка на «PyObject_CallObject»
...
Мой код:
mftest.cpp:
#include "mathFunProvider.h"
int main() {
mathFunProvider MFP;
auto result = MFP.load_scripts("/home/ivan/CppProjects/calc/test_scripts");
return 0;
}
mathFunProvider.h:
#ifndef CALC_MATHFUN_PROVIDER
#define CALC_MATHFUN_PROVIDER
#include <Python.h>
#include "expression/math_fun.h"
#include <map>
#include <filesystem>
#include <vector>
/// @brief Запускает окружение Python, загружает пользовательские функции при создании;
/// завершает работу с окружением Python при уничтожении, реализуя таким образом принцип RAII
class mathFunProvider {
std::vector<PyObject*> loaded_funs;
static PyObject* vector_to_py_list(std::vector<double> source);
/// @brief Загружает одну функцию с заданным именем из модуля
/// @param module_attr_name Python-строка, название функции, которую нужно загрузить
/// @param module объект модуля
/// @returns Пару <название функции>, <функтор>
std::pair<std::string, mathFun> load_new_fun(PyObject* module, PyObject* module_attr_name);
public:
mathFunProvider();
~mathFunProvider();
/// @brief Загружает пользовательские функции, определенные в .py-модулях, лежащих в заданной папке.
/// Python-функции должны принимать на вход список вещественных чисел (List[float]) и возвращать float
/// @param script_folder папка, в которой хранятся Python-модули
/// @return Словарь, в котором названию каждой Python-функции соответствует объект mathFun
std::map <std::string, mathFun> load_scripts(std::filesystem::path script_folder);
};
#endif
mathFunProvider.cpp:
#include <Python.h>
#define PY_SSIZE_T_CLEAN
#include "mathFunProvider.h"
mathFunProvider::mathFunProvider(){
Py_Initialize();
}
mathFunProvider::~mathFunProvider(){
Py_Finalize();
}
PyObject* mathFunProvider::vector_to_py_list(std::vector<double> source){
PyObject* R = PyList_New(0);
for (double item: source)
PyList_Append(R, PyFloat_FromDouble(item));
Py_XINCREF(R);
return R;
}
std::pair<std::string, mathFun> mathFunProvider::load_new_fun(PyObject* module, PyObject* module_attr_name){
PyObject* attr = PyObject_GetAttr(module, module_attr_name);
if (PyCallable_Check(attr)) {
PyObject* fun_name = PyUnicode_AsASCIIString(module_attr_name);
// Represents __code__ attr of a Python function
PyObject* code_attr = PyObject_GetAttrString(attr, "__code__");
int args_num = PyLong_AsLong(PyObject_GetAttrString(code_attr, "co_argcount"));
loaded_funs.push_back(attr);
auto callback = std::bind([](PyObject* attr, std::vector<double> args) -> double {
PyObject* converted_args = vector_to_py_list(args);
PyObject* R = PyObject_CallObject(attr, converted_args);
Py_XDECREF(converted_args);
double answer = PyFloat_AsDouble(R);
Py_XDECREF(R);
return answer;
}, loaded_funs.back(), std::placeholders::_1);
return {PyBytes_AsString(fun_name),
mathFun(callback, args_num)};
}
else throw;
}
std::map <std::string, mathFun> mathFunProvider::load_scripts(std::filesystem::path script_folder){
std::map <std::string, mathFun> R;
PyObject* cur_module;
PyObject* cur_module_path;
std::filesystem::path cwd = std::filesystem::current_path();
std::filesystem::current_path(script_folder);
PyObject* const PY_SLASH_STR = PyUnicode_FromString("/");
PyObject* const PY_DOT_STR = PyUnicode_FromString(".");
PyObject* module_attrs;
PyObject* module_attr_names;
PyObject* cur_attr_name;
for (auto const& dir_item: std::filesystem::recursive_directory_iterator(script_folder)){
if (dir_item.path().extension() == ".py") {
cur_module_path = PyUnicode_FromString(dir_item.path().stem().c_str());
cur_module = PyImport_Import(PyUnicode_Replace(cur_module_path, PY_SLASH_STR, PY_DOT_STR, -1));
module_attrs = PyObject_GenericGetDict(cur_module, NULL);
module_attr_names = PyMapping_Keys(module_attrs);
if (module_attr_names) {
int module_attr_len = PySequence_Length(module_attr_names);
for (int i = 0; i < module_attr_len; i++){
cur_attr_name = PySequence_GetItem(module_attr_names, i);
R.insert(load_new_fun(cur_module, cur_attr_name));
}
}
else throw;
}
}
Py_XDECREF(PY_SLASH_STR);
Py_XDECREF(PY_DOT_STR);
Py_XDECREF(module_attrs);
Py_XDECREF(module_attr_names);
Py_XDECREF(cur_attr_name);
std::filesystem::current_path(cwd);
return R;
}
Ubuntu 20, установлены Python 3.8 и 3.12, компилятор g++. Пробовал несколько способов собрать:
g++ -fPIE -fpic --std=c++2a $(pkg-config --cflags python-3.12) mathFunProvider.cpp mftest.cpp $(pkg-config --libs python-3.12) -o mftest
g++ -fPIE -fpic --std=c++2a mathFunProvider.cpp mftest.cpp $(python3-config --ldflags --cflags) -o mftest
g++ -fPIE -fpic --std=c++2a mathFunProvider.cpp mftest.cpp $(python3-config --ldflags --includes --cflags) -o mftest
Пожалуйста, объясните, как скомпилировать мой код? Не понимаю, что я делаю не так.
Ответы (1 шт):
Автор решения: Alex F
→ Ссылка
Добавьте --emded флаг к python3-config:
$(python3-config --ldflags --emded)
В соответствии с абсолютно неясной документацией python-config, этот флаг нужен для программ, которые включают интерпретатор Python. Возможно, команда python3-config --help печатает более понятную информацию.