Como usar bibliotecas feitas em C no Python
A maioria das linguagens de programação atuais são escritas em C, e o Python não é diferente, e como Ruby ou Go o Python tem uma ligação especial com a linguagem, da para usar bibliotecas do C dentro do Python, é o que chamamos de binding.
Como funciona essa “bruxaria”
Quando Guido Van Rossum idealizou o Python, ele usou o C como base de sua argumentação, já que segundo ele o C tem programas muito grandes e muitas vezes difíceis de entender. E então criou o Python para ser uma linguagem simples e de fácil compreensão, e ele conseguiu, mas com isso perdeu todas as vantagens que existem em usar o C, como acesso direto à memória e aos componentes de hardware do sistema (sem falar da performance e velocidade, porque vamos concordar que Python é uma carroça).
A solução para isso foi permitir que fosse possível usar bindings de C dentro do Python, isso é possível por que todo o código que você escreve em Python é “compilado” para um binário PYC, uma espécie de bytecode do python, e para usar uma biblioteca do C junto com o PYC o interpretador usa linka abiblioteca C junto com esse bytecode, e é por isso que o interpretador cria uma pasta “pycache” sempre que tem importação de bibliotecas pro seu script python.
Mão do código!
Antes de mais nada instale a biblioteca com a API para o C, é basicamente os arquivos de header que o python disponibiliza para a criação dos bidings:
Parece que você não instalou corretamente os arquivos de cabeçalho e as bibliotecas estáticas do python dev. Use seu gerenciador de pacotes para instalá-los em todo o sistema.
Para apt
( Ubuntu, Debian ... ):
sudo apt-get install python-dev # for python2.x installs
sudo apt-get install python3-dev # for python3.x installs
Para yum
( CentOS, RHEL ... ):
sudo yum install python-devel # for python2.x installs
sudo yum install python3-devel # for python3.x installs
Para dnf
( Fedora ... ):
sudo dnf install python2-devel # for python2.x installs
sudo dnf install python3-devel # for python3.x installs
Para zypper
( openSUSE ... ):
sudo zypper in python-devel # for python2.x installs
sudo zypper in python3-devel # for python3.x installs
Para apk
( Alpine ... ):
# This is a departure from the normal Alpine naming
# scheme, which uses py2- and py3- prefixes
sudo apk add python2-dev # for python2.x installs
sudo apk add python3-dev # for python3.x installs
Para apt-cyg
( Cygwin ... ):
apt-cyg install python-devel # for python2.x installs
apt-cyg install python3-devel # for python3.x installs
A segunda coisa a fazer é o módulo, em um programa C normal ela ficaria assim:
#include <stdlib.h>
int main(int ac, char ** av){
return system(av[0]);
}
Para transformá-lo em um módulo Python, temos que incluir o header “Python.h”, que contém a API do Python e nos proverá todos os métodos, macros e tipos necessários para fazer um módulo Python em C.
#include <stdlib.h>
#include <Python.h>
Depois escrevemos a função run
, que vai “rodar” os comandos passados para ela através de uma string, ela vai ser a função que irá chamar a nossa função system
do codigo em C. Como no Python tudo é um objeto a função retorna sempre um ponteiro para um PyObject
(um objeto python), sempre recebe 2 parâmetros, o primeiro é o famoso self
usado para referenciar items em uma classe Python, e o segundo é os argumentos que a função vai receber, ambos são também ponteiros de PyObject
, isso vai ficar mais ou menos assim:
PyObject * run (PyObject * self, PyObject * args){
Depois nós criamos a variável cmd
, que vai receber a string esperada dos argumentos (args
):
char * cmd;
if (!PyArg_ParseTuple(args, "s", &cmd)) // se a função retornar um erro
exit (1); // finaliza o programa com um erro
/*
* A função PyArg_ParseTuple sempre recebe a variável que representa a
* lista de argumentos da função, uma string com os tipos das variáveis
* que vão guardar os argumentos, e as variáveis.
*
* Esta string pode receber "s","i" ou "f" que equivalem respectivamente
* string, inteiro e real.
*/
Depois é só retornar o resultado da função system
(um inteiro que indica se o comando foi executado com sucesso ou não) convertido para um PyLong, o equivalente a um inteiro no C:
return PyLong_FromLong( system(cmd) );
E a nossa função ficou assim:
PyObject *run(PyObject *self, PyObject *args) {
char * cmd;
if(!PyArg_ParseTuple(args, "s", &cmd))
exit (1);
return PyLong_FromLong( system(cmd) );
}
Agora parece mais simples né?
Agora temos que criar a lista de métodos que o nosso módulo vai ter:
static PyMethodDef shellMethods[] = {
{
"run", // nome da função no python
run, // nome da função no C
METH_VARARGS, // diz que é um método com argumentos
"Run shell command" // descrição da função
},
{NULL, NULL, 0, NULL} // simboliza o fim da lista de funções
};
E finalmente vamos finalizar o módulo dando as suas propriedades e criando o método init dele:
static struct PyModuleDef shell = {
PyModuleDef_HEAD_INIT,
"shell", // nome do módulo
"Shell module", // descrição
-1, // nível do escopo do módulo (-1 é global)
shellMethods // lista com as funções do módulo
};
// método __init__
PyMODINIT_FUNC PyInit_shell (void) {
return PyModule_Create(&shell);
}
Agora é só criar um setup.py com as configurações do módulo:
from distutils.core import setup, Extension
def main():
setup(
author="<You Name>",
author_email="<You Email>",
name="shell", # nome do módulo
version="1.0.0", # versão
description="Run shell commands", # descrição
ext_modules=[Extension("shell", ["shell.c"])] # lista de módulos
)
if _name__ == "__main__":
main()
Para compilar e instalar o módulo é só rodar:
$ python3 setup.py install --user
E para usar é só abrir o terminal interativo do Python e digitar:
>>> import shell
>>> shell.run("uname -a")
Linux Karen 5.4.8-zen1-1-zen #1 ZEN SMP PREEMPT Sat, 04 Jan 2020 23:38:36 +0000 x86_64 GNU/Linux
0
Para mais informações sobre o uso deste recurso maravilhoso do Python consulte a documentação oficial.