Como usar bibliotecas feitas em C no Python

Yaks Souza
5 min readDec 14, 2020

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.

--

--

Yaks Souza

Hello, I’m Yak’s Souza and a developer without cetificate, I’m a curious, a dreamer who dreams too high and ends up falling over and over, but I love what I do.