Python из C
- четверг, 5 сентября 2019 г. в 00:34:54
 

В прошлом году появилась необходимость дополнить старый проект написанный на C функционалом на python3. Не смотря на то, что есть статьи на эту тему я помучился и в том году и сейчас когда писал программы для статьи. Поэтому приведу свои примеры по тому как работать с python3 из C под Linux (с тем что использовал). Опишу как создать класс и вызвать его методы, получить доступ к переменным. Вызов функций и получение переменных из модуля. А также проблемы с которыми я столкнулся и не смог их понять.
Используем стандартное Python API. Необходимые пакеты python:
Самое простое, загрузка интерпретатора python и работа в нем.
Для работы необходимо подключить заголовочный файл:
 #include <Python.h>Загружаем интерпретатор:
Py_Initialize();Далее идет блок работы с python, например:
PyRun_SimpleString("print('Hello!')");Выгружаем интерпретатор:
Py_Finalize();Полный пример:
#include <Python.h>
void
main() {
    // Загрузка интерпретатора Python
    Py_Initialize();
    // Выполнение команды в интерпретаторе
    PyRun_SimpleString("print('Hello!')");
    // Выгрузка интерпретатора Python
    Py_Finalize();
}Как компилировать и запустить:
gcc simple.c $(python3-config --includes --ldflags) -o simple && ./simple
Hello!А вот так не будет работать:
gcc $(python3-config --includes --ldflags)  simple.c -o simple && ./simple
/tmp/ccUkmq57.o: In function `main':
simple.c:(.text+0x5): undefined reference to `Py_Initialize'
simple.c:(.text+0x16): undefined reference to `PyRun_SimpleStringFlags'
simple.c:(.text+0x1b): undefined reference to `Py_Finalize'
collect2: error: ld returned 1 exit statusВсе из-за того, что python3-config --includes --ldflags раскрывается вот в такую штуку:
-I/usr/include/python3.6m -I/usr/include/python3.6m
-L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functionsЗдесь думаю важен порядок подключения линкера -Wl. Кто знает точнее напишите про это в коментах, дополню ответ.
Пример вызова функции из файла python:
simple.c
#include <Python.h>
void 
python() {
    // Загрузка интерпретатора Python
    Py_Initialize();
    // Выполнение команд в интерпретаторе
    // Загрузка модуля sys
    PyRun_SimpleString("import sys");
    // Подключаем наши исходники python
    PyRun_SimpleString("sys.path.append('./src/python')");
    PyRun_SimpleString("import simple");
    PyRun_SimpleString("print(simple.get_value(2))");
    PyRun_SimpleString("print(simple.get_value(2.0))");
    PyRun_SimpleString("print(simple.get_value(\"Hello!\"))");
    // Выгрузка интерпретатора Python
    Py_Finalize();  
}
void
main() {
    puts("Test simple:");
    python();
}simple.py
#!/usr/bin/python3
#-*- coding: utf-8 -*-
def get_value(x):
    return xНо это простые и неинтересные вещи, мы не получаем результат выполнения фунции.
Здесь немного сложнее.
Загрузка интерпретатора python и модуля func.py в него.:
PyObject *
python_init() {
    // Инициализировать интерпретатор Python
    Py_Initialize();
    do {
        // Загрузка модуля sys
        sys = PyImport_ImportModule("sys");
        sys_path = PyObject_GetAttrString(sys, "path");
        // Путь до наших исходников python
        folder_path = PyUnicode_FromString((const char*) "./src/python");
        PyList_Append(sys_path, folder_path);
        // Загрузка func.py
        pName = PyUnicode_FromString("func");
        if (!pName) {
            break;
        }
        // Загрузить объект модуля
        pModule = PyImport_Import(pName);
        if (!pModule) {
            break;
        }
        // Словарь объектов содержащихся в модуле
        pDict = PyModule_GetDict(pModule);
        if (!pDict) {
            break;
        }
        return pDict;
    } while (0);
    // Печать ошибки
    PyErr_Print();
}Освобождение ресурсов интерпретатора python:
void
python_clear() {
    // Вернуть ресурсы системе
    Py_XDECREF(pDict);
    Py_XDECREF(pModule);
    Py_XDECREF(pName);
    Py_XDECREF(folder_path);
    Py_XDECREF(sys_path);
    Py_XDECREF(sys);
    // Выгрузка интерпретатора Python
    Py_Finalize();
}Работа с переменными и функциями модуля.
/**
 * Передача строки в качестве аргумента и получение строки назад
 */
char *
python_func_get_str(char *val) {
    char *ret = NULL;
    // Загрузка объекта get_value из func.py
    pObjct = PyDict_GetItemString(pDict, (const char *) "get_value");
    if (!pObjct) {
        return ret;
    }
    do {
        // Проверка pObjct на годность.
        if (!PyCallable_Check(pObjct)) {
            break;
        }
        pVal = PyObject_CallFunction(pObjct, (char *) "(s)", val);
        if (pVal != NULL) {
            PyObject* pResultRepr = PyObject_Repr(pVal);
            // Если полученную строку не скопировать, то после очистки ресурсов python её не будет.
            // Для начала pResultRepr нужно привести к массиву байтов.
            ret = strdup(PyBytes_AS_STRING(PyUnicode_AsEncodedString(pResultRepr, "utf-8", "ERROR")));
            Py_XDECREF(pResultRepr);
            Py_XDECREF(pVal);
        } else {
            PyErr_Print();
        }
    } while (0);
    Py_XDECREF(pObjct);
    return ret;
}
/**
 * Получение значения переменной содержащей значение типа int
 */
int
python_func_get_val(char *val) {
    int ret = 0;
    // Получить объект с именем val
    pVal = PyDict_GetItemString(pDict, (const char *) val);
    if (!pVal) {
        return ret;
    }
    // Проверка переменной на long
    if (PyLong_Check(pVal)) {
        ret = _PyLong_AsInt(pVal);
    } else {
        PyErr_Print();
    }
    Py_XDECREF(pVal);
    return ret;
}На этом остановимся подробнее
pVal = PyObject_CallFunction(pObjct, (char *) "(s)", val);"(s)" означает, что передается 1 параметр типа *char в качестве аргумента функции get_value(x)**. Если бы нам нужно было передать несколько аргументов функции, то было бы так:
pVal = PyObject_CallFunction(pObjct, (char *) "(sss)", val1, val2, val3);Если необходимо передать int, то использовалась бы литера i, все возможные типы данных и их обозначения можно посмотреть в документации python.
pVal = PyObject_CallFunction(pObjct, (char *) "(i)", my_int);func.py:
#!/usr/bin/python3
#-*- coding: utf-8 -*-
a = 11
b = 22
c = 33
def get_value(x):
    return x
def get_bool(self, x):
    if x:
        return True
    else:
        return FalseПроблема с которой я столкнулся и не смог пока понять:
int
main() {
    puts("Test func:");
    if (!python_init()) {
        puts("python_init error");
        return -1;
    }
    puts("Strings:");
    printf("\tString: %s\n", python_func_get_str("Hello from Python!"));
    puts("Attrs:");
    printf("\ta: %d\n", python_func_get_val("a"));
    printf("\tb: %d\n", python_func_get_val("b"));
    printf("\tc: %d\n", python_func_get_val("c"));
    python_clear();
    return 0;
}Если хочу получить b или c из func.py, то на:
Py_Finalize();получаю segmentation fault. С получением только a такой проблемы нет.
При получении переменных класса, тоже проблем нет.
Тут еще немножечко посложнее.
Загрузка интерпретатора python и модуля class.py в него.
PyObject *
python_init() {
    // Инициализировать интерпретатор Python
    Py_Initialize();
    do {
        // Загрузка модуля sys
        sys = PyImport_ImportModule("sys");
        sys_path = PyObject_GetAttrString(sys, "path");
        // Путь до наших исходников python
        folder_path = PyUnicode_FromString((const char*) "./src/python");
        PyList_Append(sys_path, folder_path);
        // Создание Unicode объекта из UTF-8 строки
        pName = PyUnicode_FromString("class");
        if (!pName) {
            break;
        }
        // Загрузить модуль class
        pModule = PyImport_Import(pName);
        if (!pModule) {
            break;
        }
        // Словарь объектов содержащихся в модуле
        pDict = PyModule_GetDict(pModule);
        if (!pDict) {
            break;
        }
        // Загрузка объекта Class из class.py
        pClass = PyDict_GetItemString(pDict, (const char *) "Class");
        if (!pClass) {
            break;
        }
        // Проверка pClass на годность.
        if (!PyCallable_Check(pClass)) {
            break;
        }
        // Указатель на Class
        pInstance = PyObject_CallObject(pClass, NULL);
        return pInstance;
    } while (0);
    // Печать ошибки
    PyErr_Print();
}Передача строки в качестве аргумента и получение строки назад
char *
python_class_get_str(char *val) {
    char *ret = NULL;
    pVal = PyObject_CallMethod(pInstance, (char *) "get_value", (char *) "(s)", val);
    if (pVal != NULL) {
        PyObject* pResultRepr = PyObject_Repr(pVal);
        // Если полученную строку не скопировать, то после очистки ресурсов python её не будет.
        ret = strdup(PyBytes_AS_STRING(PyUnicode_AsEncodedString(pResultRepr, "utf-8", "ERROR")));
        Py_XDECREF(pResultRepr);
        Py_XDECREF(pVal);
    } else {
        PyErr_Print();
    }
    return ret;
}Здесь ни каких проблем не было, все работает без ошибок. В исходниках примеры, как работать с int, double, bool.
Пока писал материал освежил свои знания )
Надеюсь будет полезно.