C / C ++ de Python (API C)

principal

Nous continuons le sujet de la façon d'appeler C / C ++ à partir de Python3 . Maintenant, nous utilisons l' API C pour créer le module, dans cet exemple, nous pouvons comprendre comment cffi et d'autres bibliothèques simplifient nos vies. Parce qu'à mon avis, c'est la voie la plus difficile.


C


Une bibliothèque de tests pour montrer comment travailler avec des variables globales, des structures et des fonctions avec des arguments de différents types. Dans mes articles, j'utilise des variantes de la même bibliothèque, selon la méthode que j'utilise maintenant. Liens vers les méthodes précédentes ci-dessous.
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); } 

Le module doit indiquer qu'il comprendra: fonctions, variables globales et structures. Chacune de ces choses doit être décrite, la chose la plus difficile pour ses types de données (structure ...) Environ un tel fichier est généré par cffi .


Pour fonctionner, vous devez connecter les fichiers d'en-tête:


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

Indicateurs de compilation:


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

La fonction suivante est responsable du traitement des arguments:


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

Le premier est un argument de type int, il a la désignation de lettre i
2e flotteur / double - d
3e chaîne - s
Toutes les désignations de lettres possibles des types de données peuvent être trouvées ici.


Passons maintenant à la description de la façon de décrire la structure.
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 */ }; 

Et c'est tout pour:


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

D'accord, pas un peu. De plus, des méthodes peuvent être définies pour la structure (par exemple, test_st_print ).
Dans le code, j'essaie de faire plus de commentaires pour décrire séparément moins.


Python


Un exemple de travail avec un module C de 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))) 

Le module est devenu natif.


Avantages et inconvénients de l'API C


Avantages :


  • facile à utiliser en python

Inconvénients :


  • difficile de décrire vos types de données dans l' API C
  • il est difficile pour les programmeurs purs d'implémenter pour Python , et pas pour eux non plus ... (pour moi, le plus simple est par le biais de ctypes )
  • module (bibliothèque) sera uniquement pour Python

Le temps d'exécution moyen des tests sur chaque méthode avec 1000 démarrages:


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

Les références


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


All Articles