Python de C (API C)

principal

L'année dernière, il était nécessaire de compléter un ancien projet écrit en C avec des fonctionnalités en Python3 . Malgré le fait qu'il y ait des articles sur ce sujet, j'ai souffert cette année-là et maintenant quand j'ai écrit des programmes pour l'article. Par conséquent, je vais donner mes exemples sur la façon de travailler avec Python3 à partir de C sous Linux (avec ce que j'ai utilisé). Je vais décrire comment créer une classe et appeler ses méthodes, pour accéder aux variables. Appeler des fonctions et obtenir des variables du module. Et aussi les problèmes que j'ai rencontrés et que je n'ai pas pu comprendre.


Forfaits


Nous utilisons l' API Python standard pour C. Packages Python requis:


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

Travailler dans l'interprète


La chose la plus simple est de charger et de travailler dans l'interpréteur Python.


Pour travailler, vous devez connecter le fichier d'en-tête:


#include <Python.h> 

Chargement de l'interpréteur:


 Py_Initialize(); 

Ensuite, un bloc de travail avec Python, par exemple:


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

Déchargez l'interprète:


 Py_Finalize(); 

Exemple complet:


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

Comment compiler et exécuter:


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

Mais cela ne fonctionnera pas:


 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 

C'est parce que python3-config --includes --ldflags se développe dans ce genre de chose:


 -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 

Ici, je pense que l'ordre de connexion de l' éditeur de liens -Wl est important . Qui sait plus précisément, écrivez à ce sujet dans les commentaires, je compléterai la réponse.


Explication de MooNDeaR :


Tout est assez simple - les personnages sont recherchés en un seul passage et tous les personnages inutilisés sont jetés. Si vous mettez simple.c à la fin, il s'avère que l'éditeur de liens verra l'utilisation du symbole Py_Initialize () après avoir regardé les bibliothèques python, dont tous les symboles seront ignorés à ce moment (car ils n'ont pas été utilisés).

Un exemple d'appel d'une fonction à partir d'un fichier 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 

Mais ce sont des choses simples et sans intérêt, nous n'obtenons pas le résultat de la fonction.


Travailler avec des fonctions et des variables de module


C'est un peu plus compliqué.
Charger l'interpréteur Python et le module func.py dedans:


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

Libération des ressources de l'interpréteur 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(); } 

Travailler avec des variables et des fonctions de module.


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

Arrêtons-nous là-dessus plus en détail.


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

"(s)" signifie que 1 paramètre char * est passé en argument à get_value (x) . Si nous devons passer plusieurs arguments à la fonction, ce sera comme ceci:


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

Si vous devez passer int , alors la lettre i serait utilisée, tous les types de données possibles et leurs désignations peuvent être trouvés dans la documentation 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 

( Problème résolu )
Le problème que j'ai rencontré et que je ne pouvais pas encore comprendre:


 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 je veux obtenir b ou c de func.py , alors:


 Py_Finalize(); 

Je reçois un défaut de segmentation . Il n'y a pas un tel problème avec seulement un .
Lors de la réception de variables de classe, il n'y a pas de problème non plus.


Explication de pwl :


PyObject PyDict_GetItemString (PyObject p, const char * key)
Valeur de retour: référence empruntée. Rien ne doit être fait pour une référence empruntée.

Le problème était que j'appelais Py_XDECREF () pour PyDict_GetItemString () . Il n'est pas nécessaire de le faire pour cette fonction, ce qui entraîne un défaut de segmentation .


Travail en classe


Il y a encore un peu plus compliqué.
Chargement de l'interpréteur Python et du module 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(); } 

Passer une chaîne en argument et récupérer la chaîne


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

Il n'y a eu aucun problème ici, tout fonctionne sans erreur. Il y a des exemples dans la source pour travailler avec int , double , bool .


En écrivant du matériel, j'ai rafraîchi mes connaissances)
J'espère que ce sera utile.


Les références


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


All Articles