Python из C

от автора

main

В прошлом году появилась необходимость дополнить старый проект написанный на C функционалом на python3. Не смотря на то, что есть статьи на эту тему я помучился и в том году и сейчас когда писал программы для статьи. Поэтому приведу свои примеры по тому как работать с python3 из C под Linux (с тем что использовал). Опишу как создать класс и вызвать его методы, получить доступ к переменным. Вызов функций и получение переменных из модуля. А также проблемы с которыми я столкнулся и не смог их понять.

Пакеты

Используем стандартное Python API. Необходимые пакеты python:

  • apt-get install python3
  • apt-get install python3-dev
  • apt-get install python3-all
  • apt-get install python3-all-dev
  • apt-get install libpython3-all-dev

Работа в интерпретаторе

Самое простое, загрузка интерпретатора 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.

Пока писал материал освежил свои знания )
Надеюсь будет полезно.

Ссылки

Исходные коды примеров


ссылка на оригинал статьи https://habr.com/ru/post/466181/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *