Python Bindings

Binding? O que é isso e para que serve?

Um binding é literalmente a ligação ou ponte entre dois sistemas. No caso de bindings para Python, chama-se de extensão a ligação entre bibliotecas desenvolvidas em C ou C++ para o uso direto no interpretador Python.

Para fazer uma extensão, é necessário “embrulhar” (wrap) a API da biblioteca através de métodos que são expostos por uma API definida em C do Python.

Por que eu devo fazer uma extensão em Python? O que ganho com isso? Talvez o motivo seja...

O propósito desse artigo é apresentar um wrapper em cima da biblioteca readline ou libedit nos sistemas BSD, por ser de fácil implementação e ter boa disponibilidade nos sistemas Unix; só é bom lembrar que o interpretador Python já provê a mesma funcionalidade com o modulo readline.

Python C API

Para trabalhar com a API de extensões deve-se incluir o cabeçalho Python.h, este arquivo inclui “de brinde” os cabeçalhos stdio.h, string.h, errno.h, limits.h e stdlib.h.

Na Python C API, todos os nomes visíveis de símbolos começam com o prefix Py e os nomes internos têm como prefixo _Py. A API disponibiliza um tipo opaco PyObject (na verdade um ponteiro para esse tipo) que é análogo aos objetos em Python.

Os objetos PyObject vivem sempre na memória heap, a exceção à regra são os objetos PyTypeObject declarados como static.

Todo o PyObject possuem um tipo associado e são os elementos utilizados como argumentos e retorno de valores para o interpretador. São definidas macros especiais para testar os tipos, por exemplo PyList_Check(array) retorna verdadeiro ou falso se a variável array for do tipo lista.

Como os objetos vivem na heap, é necessário fazer o controle manual de referências, através das macros Py_INCREF e Py_DECREF, essa é uma tarefa propensa a erros de vazamento de memória.

Vamos parar um pouco com a teoria e ir para prática. No arquivo de inclusão readline.h podemos ver a declaração das funções:

char *readline(const char *);
int add_history(const char *);

Vejamos como fazer o wrapper de readline()

#include <Python.h>
#include <readline/readline.h>

PyObject *
fn_readline(PyObject *self, PyObject *args)
{
  char *prompt;
  char *line;
  PyObject *o;

  if (!PyArg_ParseTuple(args, "s", &prompt))
    return NULL;

  line = readline(prompt);
  
  o = Py_BuildValue("s", line);
  free(line);

  return o;
}

Continuaremos para a função add_history():

PyObject *
fn_add_history(PyObject *self, PyObject *args)
{
  char *line;
  int status;
  PyObject *o;

  if (!PyArg_ParseTuple(args, "s", &line))
    NULL;

  status = add_history(line);

  o = Py_BuildValue("i", status);
  return o;
}

Basicamente igual a função anterior, a única diferença é o retorno de um valor inteiro em vez de uma string. Esse valor não tem muita importância, poderia-se retornar um None, mas o None possui uma peculiaridade, vejamos...

// Versão alternativa, retorna None

PyObject *
fn_add_history(PyObject *self, PyObject *args)
{
  char *line;

  if (!PyArg_ParseTuple(args, "s", &line))
    NULL;

  add_history(line);

  Py_INCREF(Py_None); 
  return Py_None; 
}

O idioma para retorna um objeto None exige o controle referências. Não se deve confundir o retorno de NULL, que repassa uma condição de erro com o retorno de None, que é um valor perfeitamente normal.

O que falta para finalizar o binding é a declaração de quais são as funções que o módulo disponibiliza e sua iniciação, vejamos agora.

static PyMethodDef
methods[] = {
  {"readline", fn_readline, METH_VARARGS, NULL},
  {"add_history", fn_add_history, METH_VARARGS, NULL},
  {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initreadline(void)
{
  (void) Py_InitModule("readline", methods);
}

Já temos tudo o que é necessário para o binding, está na hora de gerar o módulo para uso no Python, esta linha de compilação funciona no Mac OS X, para o Linux troque -dynamiclib por -PIC.

$ gcc -dynamiclib -I/usr/include/python2.5 -lpython -lreadline mod_readline.c -o readline.so

Testando...

>>> import readline
>>> dir(readline)
['__doc__', '__file__', '__name__', 'add_history', 'readline']
>>> line = readline.readline("prompt$ ")
prompt$ Sisters of Mercy
>>> print line
Sisters of Mercy
>>> readline.add_history("Bird On The Wire")
0
>>> readline.add_history("So Long, Marianne")
0
>>> readline.readline("] ")
] seta para cima navega no histórico

Quais são as vantagens de se usar a API em C?

Quais são as desvantagens?

SWIG

O SWIG (Simplified Wrapper and Interface Generator) é um sistema que permite gerar bindings de bibliotecas em C ou C++ para diversas linguagens de programação, em especial olharemos os requisitos de uso do SWIG para a linguagem Python.

O SWIG é um projeto maduro, surgiu em 1995 e praticamente automatiza o processo de criação de módulos de extensão em Python.

O processo de geração dos bindings começa com a definição de um arquivo contendo as inclusões das bibliotecas que se deseja fazer o binding, além de outra seção que permite filtrar quais elementos da biblioteca serão expostos para os bindings.

O SWIG trabalha diretamente com os arquivos #include, portanto um bom conhecimento de C faz-se necessário. Em baixo nível, o SWIG gera código em C de cola para a API em C do Python.

O resultado do processamento do utilitário swig é um arquivo em C que deve ser compilado e linkado diretamente com a biblioteca que se deseja fazer binding e o arquivo em Python que expõe a API como um módulo em Python.

Arquivo de interface readline.i do binding para o SWIG. Este arquivo possui três partes lógicas:

Embora o arquivo readline.h contenha várias funções e variáveis, nós só estamos interessados apenas em expor as funções readline() e add_history().

// readline.i

%module readline

%{
#include <readline/readline.h>
%}

char *readline(const char *);
int add_history(const char *);

O processo completo para a geração do binding é então:

$ swig -python readline.i
$ gcc -c readline_wrap.c  -I/usr/include/python2.5
$ gcc -dynamiclib readline_wrap.o -lpython -lreadline -o _readline.so

No caso do exemplo de readline, são gerados o arquivo em C readline_wrap.c e o módulo em Python que se chama readline.py.

Exemplo de uso:

>>> import readline
>>> readline.add_history("quit")
0
>>> line = readline.readline("] ")
] quit  tecla para cima
>>> print line
quit
>>> print type(line)
<type 'str'>

A maneira mais fácil de usar o SWIG é incluir literalmente o conteúdo do arquivo ou dos arquivos de inclusão da biblioteca que se deseja fazer o binding no arquivo de interface, pode ser um tiro certo fazer isso. Algo como:

%module readline

%{
#include <readline/readline.h>
#include <readline/history.h>
%}

%include /usr/include/readline/readline.h
%include /usr/include/readline/history.h

Infelizmente o processo falha miseravelmente, mas não é objetivo desse artigo explicar o porque.

Quais são as vantagens do SWIG?

Quais são as desvantagens do SWIG?