Diego Rubin

Fullstack Developer

Entre em contato

Rodando Código Python Dentro de Uma Aplicação C

Ereader icon

O sistema que estou desenvolvendo para o meu TCC terá grande parte de sua lógica implementada em Python e outra parte será implementada em C/C++, para isso preciso embeddar código Python dentro da aplicação.

Esta forma de programação é utilizada em diversas aplicações e é muito utilizada para poder gerar recursos para a criação de plugins, como é feito, por exemplo, no gedit e no gnome-panel(pelo menos na versão do gnome2, não sei na 3).

Neste post quero dar alguns exemplos simples de como podemos realizar esta tarefa.

Codificando

Nos exemplos deste post estarei utilizando a versão 2.7 do Python e é necessário ter os includes e as bibliotecas compartilhadas instalados, geralmente estão no pacote de desenvolvimento do Python de sua distriuição favorita( ou a que você esta utilizando =) ).

A baixo está um exemplo bem simples, onde as entradas lidas e passadas para o interpretador python executa-las.

/*
    file: pyConsole.c
*/

#include <stdio.h>
#include <python2.7/Python.h>

int main(){

    char command[200]; 

    Py_Initialize();
    for(;;){
        printf("-> ");
        scanf("%200[^\n]%*c",command);
        PyRun_SimpleString(command);
    }
    Py_Finalize();

    return 0;
}

Para compilar o código acima o seguinte comando deve ser executado:

gcc pyConsole.c -o pyConsole -g -Wall -lpython2.7 -lm -L/usr/lib/python2.7/config
#para execução
./pyConsole

Para sair do programa podemos utilizar a função quit() do Python. Neste pequeno console podemos executar qualquer comando Python, mas não poderemos criar blocos de comandos ou classes pois, como o próprio nome sugere, a função PyRun_SimpleString executa apenas uma string passada. A função Py_Initialize inicialização o ambiente Python e a função Py_Finalize encerra a mesma, todas as variáveis alocadas dentro do PyRun_SimpleString serão desalocadas após o Py_Finalize. Bem, o que fizemos nesse primeiro exemplo foi criar um console do Python bem podrinho e isso poderia ser feito de diversas formas possíveis sem utilizar a libpython. O que queremos realmente é uma interação entre o programa C e o programa escrito em Python, como por exemplo recuperar alguma variável. Vejamos agora um exemplo mais complexo. O programa abaixo irá pegar um arquivo texto qualquer, trocar uma string por outra e salvar em um novo arquivo. A lógica será escrita em Python e a coleta dos dados e impressão na tela será feito em C. A função em Python ira receber os parâmetros, abrir o arquivo especificado substituir o texto, salvar em outro arquivo e retornar o texto substituído.

# file: trocar_autor.py
def trocar(entrada,saida,exp,rep):
    texto = open(entrada,"r").read()
    texto_saida = texto.replace(exp,rep)
    
    arquivo_saida = open(saida,"w")
    arquivo_saida.write(texto_saida)

    return texto_saida
/*
  file: trocar_autor.c
*/
#include <stdio.h>
#include <python2.7/Python.h>

int main(){

    PyObject *main_module, *dict;
    PyObject *trocar_autor, *resultado;

    char expressao[200];
    char substituto[200];
    char entrada[200];
    char saida[200];

    char *c_resultado;

    
    FILE *python;

    printf("Digite o nome da variavel\n");
    scanf("%200[^\n]%*c",expressao);

    printf("Digite o nome do valor\n");
    scanf("%200[^\n]%*c",substituto);

    printf("Arquivo de entrada\n");
    scanf("%200[^\n]%*c",entrada);

    printf("Arquivo de saida\n");
    scanf("%200[^\n]%*c",saida);

    Py_Initialize();
    main_module = PyImport_AddModule("__main__");
    dict = PyModule_GetDict(main_module);
   
    python = fopen("trocar_autor.py", "r");
    PyRun_SimpleFile(python, "trocar_autor.py");


    trocar_autor = PyDict_GetItemString(dict, "trocar");
    resultado = PyObject_CallFunction(trocar_autor,"ssss",entrada,saida,expressao,substituto);
    
    c_resultado = PyString_AsString(resultado);

    Py_Finalize();

    printf("--Texto Formatado--\n");
    printf("%s\n",c_resultado);

    return 0;
}

Vamos a algumas explicações.

Diferente do primeiro exemplo, aqui não passamos uma string com o código, mas executamos um arquivo com o código( função PyRun_SimpleFile ), quando essa função é executada a função trocar é declarada. Para a função python ser executada precisamos ter a referencia da mesma, para isso precisamos ter o dicionário de declarações do escopo e as funções PyImport_AddModule e PyModule_GetDict realizam está tarefa. Com o dicionário pegamos a referencia da função através da função PyDict_GetItemString. Tendo a referencia da função, utilizamos PyObject_CallFunction para passar os parâmetros e recuperar o resultado. O resultado é retornado como uma estrutura PyObject. E por fim, para printarmos o resultado através do printf temos que converter o PyObject para um string através da função PyString_AsString.

Conclusões

Há muito mais do que foi mostrado aqui esta é uma pequena introdução.

Os códigos utilizados neste post estão no repositório do blog no github e a documentação da api pode ser encontrado aqui.

Novo Comentário

Comentários

Muito bom cara! Mais um Blog pra minha lista do GoogleReader! Post mais sobre Python...

Até mais...

Muito Bom! Contudo eu tenho uma dúvida, quando importo a biblioteca dá um erro LONG_BIT, acredito que devido a arquitetura do meu sistema, você teve este problema? Conseguiu resolve-lô?

Kenad,

Não acontece comigo não. Inclusive estava fazendo algo parecido hoje mesmo para um projeto.

Teria como você postar o erro completo da compilação?

[]'s

Gustavo Bittencourt

Parabéns pelo trabalho, Diego!

Fiz algumas adaptações para funcionar em C++ e com o Python3, e foi muito interessante! Se quiser posso te passar o código. =)

Você saberia me dizer se é possível fazer algo similar usando o Cython e inserindo as bibliotecas do Python junto ao código C, para que eu tenha um código mais eficiente (cdef do Cython faz milagres!) e portável para máquinas sem o Python instalado?

Caso positivo, agradeceria muito se pudesse me enviar um e-mail!

Abraços,

Gustavo

Obrigado Gustavo.

É, realmente, este código está ficando um pouco velho já. Vão fazer 5 anos já que escrevi esse post(bateu um sentimento de que estou ficando velho de repente :( ), mas de qualquer forma fico feliz que ele ainda seja útil, mesmo que como ponto de partida.

Se você puder me mandar o código que escreveu, ficaria feliz em coloca-lo no post com os devidos créditos.

Quando ao Cython, não cheguei a utiliza-lo ainda. Sendo sincero, descobri a existência dele um pouco menos de um ano atrás. E infelizmente não poderei ajudá-lo com isso. Mas de qualquer forma vou ver de usá-lo em um projeto que pretendo iniciar mais pro final da semana que vem. Ai a gente pode conversar novamente.

Obrigado novamente pelo comentário mano.

[]'s