C / C ++ von Python (C API)

Haupt

Wir setzen das Thema fort, wie C / C ++ von Python3 aufgerufen wird . Jetzt verwenden wir die C-API , um das Modul zu erstellen. In diesem Beispiel können wir herausfinden, wie CFFI und andere Bibliotheken unser Leben vereinfachen. Denn meiner Meinung nach ist dies der schwierigste Weg.


C.


Eine Testbibliothek zur Demonstration der Arbeit mit globalen Variablen, Strukturen und Funktionen mit Argumenten verschiedener Typen. In meinen Artikeln verwende ich Variationen derselben Bibliothek, abhängig von der Methode, die ich jetzt verwende. Links zu früheren Methoden unten.
test.h


typedef struct test_st_s test_st_t; extern int a; extern double b; extern char c; static PyObject *func_hello(PyObject *self, PyObject *args); static PyObject *func_ret_int(PyObject *self, PyObject *args); static PyObject *func_ret_double(PyObject *self, PyObject *args); static PyObject *func_ret_str(PyObject *self, PyObject *args); static PyObject *func_many_args(PyObject *self, PyObject *args); static PyObject *func_ret_struct(PyObject *self, PyObject *args); struct test_st_s { PyObject_HEAD //    ,    int val1; double val2; char val3; }; 

test.c


 //    static PyMethodDef methods[] = { {"func_hello", func_hello, METH_NOARGS, "func_hello"}, //    {"func_ret_int", func_ret_int, METH_VARARGS, "func_ret_int"}, //    {"func_ret_double", func_ret_double, METH_VARARGS, "func_ret_double"}, {"func_ret_str", func_ret_str, METH_VARARGS, "func_ret_str"}, {"func_many_args", func_many_args, METH_VARARGS, "func_many_args"}, {"func_ret_struct", func_ret_struct, METH_VARARGS, "func_ret_struct"}, {NULL, NULL, 0, NULL} }; //   static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, "_test", "Test module", -1, methods }; //   PyMODINIT_FUNC PyInit__test(void) { PyObject *mod = PyModule_Create(&module); //    PyModule_AddObject(mod, "a", PyLong_FromLong(a)); // int PyModule_AddObject(mod, "b", PyFloat_FromDouble(b)); // double PyModule_AddObject(mod, "c", Py_BuildValue("b", c)); // char //   //    if (PyType_Ready(&test_st_t_Type) < 0) return NULL; Py_INCREF(&test_st_t_Type); PyModule_AddObject(mod, "test_st_t", (PyObject *) &test_st_t_Type); return mod; } /** *  ,  . */ int a = 5; double b = 5.12345; char c = 'X'; // 88 static PyObject * func_hello(PyObject *self, PyObject *args) { //   args,   warning  . puts("Hello!"); Py_RETURN_NONE; } /** *       int   . */ static PyObject * func_ret_int(PyObject *self, PyObject *args) { int val; //  -  if (PyTuple_Size(args) != 1) { PyErr_SetString(self, "func_ret_int args error"); } PyArg_ParseTuple(args, "i", &val); /* *  . * //   PyObject *obj = PyTuple_GetItem(args, 0); //     int/long if (PyLong_Check(obj)) { PyErr_Print(); } //  (PyObject *)  int val = _PyLong_AsInt(obj); */ printf("C get func_ret_int: %d\n", val); return Py_BuildValue("i", val); } /** *       double   . */ static PyObject * func_ret_double(PyObject *self, PyObject *args) { double val; if (PyTuple_Size(args) != 1) { PyErr_SetString(self, "func_ret_double args error"); } PyArg_ParseTuple(args, "d", &val); printf("C get func_ret_double: %f\n", val); return Py_BuildValue("f", val); } /** *  string   . */ static PyObject * func_ret_str(PyObject *self, PyObject *args) { char *val; if (PyTuple_Size(args) != 1) { PyErr_SetString(self, "func_ret_str args error"); } PyArg_ParseTuple(args, "s", &val); /* *  . * PyObject *obj = PyTuple_GetItem(args, 0); PyObject* pResultRepr = PyObject_Repr(obj); val = PyBytes_AS_STRING(PyUnicode_AsEncodedString(pResultRepr, "utf-8", "ERROR")); */ printf("C get func_ret_str: %s\n", val); return Py_BuildValue("s", val); } /** *       int, double, char *. */ static PyObject * func_many_args(PyObject *self, PyObject *args) { int val1; double val2; char *val3; if (PyTuple_Size(args) != 3) { PyErr_SetString(self, "func_ret_str args error"); } PyArg_ParseTuple(args, "ids", &val1, &val2, &val3); printf("C get func_many_args: int - %d, double - %f, string - %s\n", val1, val2, val3); return Py_BuildValue("ifs", val1, val2, val3); } static PyObject * func_ret_struct(PyObject *self, PyObject *args) { test_st_t *st; //    Python if (!PyArg_ParseTuple(args, "O", &st)) // O -   Py_RETURN_NONE; printf("C get test_st: val1 - %d, val2 - %f, val3 - %d\n", st->val1++, st->val2++, st->val3++); return Py_BuildValue("O", st); } 

Das Modul muss angeben, dass es Folgendes enthält: Funktionen, globale Variablen und Strukturen. Jedes dieser Dinge muss beschrieben werden, das Schwierigste für seine Datentypen (Struktur ...). Ungefähr eine solche Datei wird von cffi generiert.


Um zu arbeiten, müssen Sie die Header-Dateien verbinden:


 #include <Python.h> #include <structmember.h> //     

Kompilierungsflags:


 $(python3-config --includes --ldflags) -fPIC 

Die folgende Funktion ist für die Verarbeitung der Argumente verantwortlich:


 PyArg_ParseTuple(args, "ids", &val1, &val2, &val3); 

Das erste ist ein Argument vom Typ int, es hat die Buchstabenbezeichnung i
2. Schwimmer / Doppel - d
3. Saite - s
Alle möglichen Buchstabenbezeichnungen von Datentypen finden Sie hier.


Nun wenden wir uns der Beschreibung der Struktur zu.
struct.c:


 //   static void test_st_t_dealloc(test_st_t* self) { Py_TYPE(self)->tp_free((PyObject*)self); } //   static PyObject * test_st_t_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { test_st_t *self; self = (test_st_t *)type->tp_alloc(type, 0); if (self != NULL) { self->val1 = 0; self->val2 = 0.0; self->val3 = 0; } return (PyObject *)self; } //  ,     static int test_st_t_init(test_st_t *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"val1", "val2", "val3", NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, "|idb", kwlist, &self->val1, &self->val2, &self->val3)) return -1; return 0; } //       static PyMemberDef test_st_t_members[] = { {"val1", T_INT, offsetof(test_st_t, val1), 0, "int"}, {"val2", T_DOUBLE, offsetof(test_st_t, val2), 0, "double"}, {"val3", T_CHAR, offsetof(test_st_t, val3), 0, "char"}, {NULL} }; //  ,    static PyObject* test_st_print(PyObject *self, PyObject *args) { test_st_t *st; //    Python if (!PyArg_ParseTuple(args, "O", &st)) // O -   Py_RETURN_NONE; printf("method: val1 - %d, val2 - %f, val3 - %d\n", st->val1++, st->val2++, st->val3++); Py_RETURN_NONE; } //   ,        ! //   ! static PyMethodDef test_st_t_methods[] = { {"print", test_st_print, METH_VARARGS, "doc string"}, {NULL} /* Sentinel */ }; //    .  , , ,   ..  .. PyTypeObject test_st_t_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_test.test_st_t", /* tp_name */ sizeof(test_st_t), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor) test_st_t_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "test_st_t objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ test_st_t_methods, /* tp_methods */ test_st_t_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc) test_st_t_init, /* tp_init */ 0, /* tp_alloc */ test_st_t_new, /* tp_new */ }; 

Und das ist alles für:


 struct test_st_s { PyObject_HEAD //    ,    int val1; double val2; char val3; }; 

Stimmen Sie zu, nicht wenig. Darüber hinaus können Methoden für die Struktur definiert werden (als Beispiel test_st_print ).
Im Code versuche ich, mehr Kommentare zu machen, um weniger separat zu beschreiben.


Python


Ein Beispiel für die Arbeit mit einem C- Modul aus Python :


 import sys import time #    _test sys.path.append('.') sys.path.append('lib/') sys.path.append('../../lib/') import _test ### ## C ### print("C API\n") print("C\n") start_time = time.time() ## #    ## print('  :') print('ret func_hello: ', _test.func_hello()) print('ret func_ret_int: ', _test.func_ret_int(101)) print('ret func_ret_double: ', _test.func_ret_double(12.123456789)) print('ret func_ret_str: ', _test.func_ret_str('Hello!')) print('ret func_many_args: ', _test.func_many_args(15, 18.1617, "Many arguments!")) ## #    ## print('\n  :') print('ret a: ', _test.a) #   . _test.a = 22 print('new a: ', _test.a) print('ret b: ', _test.b) print('ret c: ', _test.c) ## #    ## print('\n  :') #   st = _test.test_st_t(1, 2.3456789, 88) print('st.val1 = {}\nst.val2 = {}\nst.val3 = {}'.format(st.val1, st.val2, st.val3)) st = _test.func_ret_struct(st) print("ret func_ret_struct:") print('st.val1 = {}\nst.val2 = {}\nst.val3 = {}'.format(st.val1, st.val2, st.val3)) #   print  ,    C   #           st.print(st) #   print("--- {} seconds ---".format((time.time() - start_time))) 

Das Modul ist nativ geworden.


Vor- und Nachteile der C-API


Vorteile :


  • einfach in Python zu verwenden

Nachteile :


  • Es ist schwierig, Ihre Datentypen in der C-API zu beschreiben
  • Für reine Programmierer ist es schwierig, Python zu implementieren, und auch nicht für sie ... (für mich ist es am einfachsten, ctypes zu verwenden. )
  • Modul (Bibliothek) ist nur für Python

Die durchschnittliche Testausführungszeit für jede Methode mit 1000 Starts:


  • ctypes: - 0,0004987692832946777 Sekunden ---
  • CFFI: - 0,00038521790504455566 Sekunden ---
  • pybind: - 0,0004547207355499268 Sekunden ---
  • C API: - 0,0003561973571777344 Sekunden ---

Referenzen


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


All Articles