Как скомпилировать программу, использующую 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 печатает более понятную информацию.

→ Ссылка