Python de C (API C)

principal

No ano passado, houve a necessidade de suplementar um projeto antigo escrito em C com funcionalidade em Python3 . Apesar do fato de haver artigos sobre esse assunto, sofri naquele ano e agora quando escrevi programas para o artigo. Portanto, darei meus exemplos de como trabalhar com Python3 de C no Linux (com o que eu usei). Vou descrever como criar uma classe e chamar seus métodos, para acessar variáveis. Chamando funções e obtendo variáveis ​​do módulo. E também os problemas que encontrei e não consegui entender.


Pacotes


Usamos a API Python padrão para C. Pacotes Python necessários:


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

Trabalho no intérprete


A coisa mais simples é carregar e trabalhar no interpretador Python.


Para funcionar, você deve conectar o arquivo de cabeçalho:


#include <Python.h> 

Carregando o intérprete:


 Py_Initialize(); 

A seguir, um bloco de trabalho com o Python, por exemplo:


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

Descarregue o intérprete:


 Py_Finalize(); 

Exemplo completo:


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

Como compilar e executar:


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

Mas isso não vai 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 

Isso ocorre porque python3-config --includes --ldflags se expande para esse tipo de coisa:


 -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 

Aqui acho que a ordem de conexão do vinculador -Wl é importante . Quem sabe mais precisamente, escreva sobre isso nos comentários, complementarei a resposta.


Explicação do MooNDeaR :


Tudo é bem simples - os caracteres são pesquisados ​​de uma só vez e todos os caracteres não utilizados são jogados fora. Se você colocar simple.c no final, o vinculador verá o uso do símbolo Py_Initialize () depois de examinar as bibliotecas python, cujos símbolos serão descartados neste momento (porque não foram usados).

Um exemplo de chamada de uma função a partir de um arquivo 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 

Mas estas são coisas simples e desinteressantes, não obtemos o resultado da função.


Trabalhar com funções e variáveis ​​de módulo


Isso é um pouco mais complicado.
Carregando o interpretador Python e o módulo func.py nele:


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

Liberando recursos do interpretador 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(); } 

Trabalhe com variáveis ​​e funções do módulo.


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

Vamos nos debruçar sobre isso com mais detalhes.


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

"(s)" significa que 1 parâmetro char * é passado como argumento para get_value (x) . Se precisarmos passar vários argumentos para a função, será assim:


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

Se você precisar passar int , a letra i seria usada, todos os tipos de dados possíveis e suas designações podem ser encontrados na documentação do 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 resolvido )
O problema que encontrei e ainda não conseguia 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; } 

Se eu quiser obter b ou c de func.py , então:


 Py_Finalize(); 

Eu recebo uma falha de segmentação . Não existe esse problema em obter apenas um .
Ao receber variáveis ​​de classe, também não há problemas.


Explicação de pwl :


PyObject PyDict_GetItemString (PyObject p, const char * chave)
Valor de retorno: referência emprestada. Nada precisa ser feito para uma referência emprestada.

O problema era que eu estava chamando Py_XDECREF () para PyDict_GetItemString () . Não é necessário fazer isso para esta função, levando a uma falha de segmentação .


Trabalho de classe


Ainda há um pouco mais complicado.
Carregando o interpretador Python e o módulo class.py nele.


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

Passando uma string como argumento e recuperando a string


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

Não houve problemas aqui, tudo funciona sem erros. Existem exemplos na fonte de como trabalhar com int , double , bool .


Enquanto escrevia o material, atualizei meus conhecimentos)
Espero que seja útil.


Referências


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


All Articles