Python desde C (C API)

principal

El año pasado, era necesario complementar un antiguo proyecto escrito en C con funcionalidad en Python3 . A pesar del hecho de que hay artículos sobre este tema, sufrí en ese año y ahora cuando escribí programas para el artículo. Por lo tanto, daré mis ejemplos sobre cómo trabajar con Python3 desde C en Linux (con lo que usé). Describiré cómo crear una clase y llamar a sus métodos, para acceder a las variables. Llamando funciones y obteniendo variables del módulo. Y también los problemas que encontré y no pude entender.


Paquetes


Utilizamos la API estándar de Python para C. Paquetes necesarios de Python:


  • python3
  • python3-dev
  • python3-all
  • python3-all-dev
  • libpython3-all-dev

Trabajar en el intérprete


Lo más simple es cargar y trabajar en el intérprete de Python.


Para trabajar, debe conectar el archivo de encabezado:


#include <Python.h> 

Cargando el intérprete:


 Py_Initialize(); 

El siguiente es un bloque de trabajo con Python, por ejemplo:


 PyRun_SimpleString("print('Hello!')"); 

Descargue el intérprete:


 Py_Finalize(); 

Ejemplo completo:


 #include <Python.h> void main() { //   Python Py_Initialize(); //     PyRun_SimpleString("print('Hello!')"); //   Python Py_Finalize(); } 

Cómo compilar y ejecutar:


 gcc simple.c $(python3-config --includes --ldflags) -o simple && ./simple Hello! 

Pero esto no funcionará:


 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 

Esto se debe a que python3-config --include --ldflags se expande en este tipo de cosas:


 -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 

Aquí creo que el orden de conexión del enlazador -Wl es importante . Quién sabe más precisamente, escriba sobre esto en los comentarios, complementaré la respuesta.


Explicación de MooNDeaR :


Todo es bastante simple: los personajes se buscan en una sola pasada y todos los caracteres no utilizados se desechan. Si pone simple.c al final, resulta que el enlazador verá el uso del símbolo Py_Initialize () después de mirar las bibliotecas de Python, todos los símbolos de los cuales serán descartados en este momento (porque no se usaron).

Un ejemplo de llamar a una función desde un archivo 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 

Pero estas son cosas simples y poco interesantes, no obtenemos el resultado de la función.


Trabajar con funciones y variables de módulo.


Esto es un poco más complicado.
Cargando el intérprete de Python y el módulo func.py en él:


 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(); } 

Liberación de recursos de intérpretes de 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(); } 

Trabajar con variables y funciones de módulos.


 /** *          */ 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); 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(); } return ret; } 

Detengámonos en esto con más detalle.


 pVal = PyObject_CallFunction(pObjct, (char *) "(s)", val); 

"(s)" significa que 1 parámetro char * se pasa como argumento para obtener_valor (x) . Si necesitamos pasar varios argumentos a la función, será así:


 pVal = PyObject_CallFunction(pObjct, (char *) "(sss)", val1, val2, val3); 

Si necesita pasar int , entonces se usaría la letra i , todos los tipos de datos posibles y sus designaciones se pueden encontrar en la documentación de 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 

( Problema resuelto )
El problema que encontré y que aún no podía entender:


 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; } 

Si quiero obtener b o c de func.py , entonces en:


 Py_Finalize(); 

Me sale un error de segmentación . No hay tal problema con obtener solo un .
Al recibir variables de clase, tampoco hay problemas.


Explicación de pwl :


PyObject PyDict_GetItemString (PyObject p, const char * key)
Valor de retorno: referencia prestada. No es necesario hacer nada para obtener una referencia prestada.

El problema era que estaba llamando a Py_XDECREF () para PyDict_GetItemString () . No es necesario hacer esto para esta función, lo que lleva a una falla de segmentación .


Trabajo en clase


Todavía hay un poco más complicado.
Cargando el intérprete de Python y el módulo class.py en él.


 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(); } 

Pasar una cadena como argumento y recuperar la cadena


 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; } 

Aquí no hubo problemas, todo funciona sin errores. Hay ejemplos en la fuente de cómo trabajar con int , double , bool .


Mientras escribía material, actualicé mis conocimientos)
Espero que te sea útil.


Referencias


Source: https://habr.com/ru/post/466181/


All Articles