Estendendo o Python 3 em Go

O prolongamento do Python tem sido uma característica central da plataforma há décadas, o tempo de execução do Python fornece uma "API C", que é um conjunto de cabeçalhos e tipos de núcleo para escrever extensões em C e compilá-las em módulos Python.

Mas, você realmente precisa escrever extensões no Python em C? Por que não podemos usar algo muito mais moderno, como Go.

Pesquisa de fundo – isso não foi feito antes?

Eu encontrei uma série de artigos antigos escritos em 2015 para o Python 2 ou para o Go 1.5. Go mudou muito desde então, então encontrar algo atualizado e bem conservado foi difícil.

Este artigo foi para o Go 1.5 e usa uma mistura de macros C e Go. Além disso, uma grande limitação do cgo vez das funções nativas do Go é que ele não suporta variadics (ou seja, *args ), o que será um grande problema na maioria dos módulos Python.

gopy , uma ferramenta para criar automaticamente módulos de pacotes Go. Parecia de um relance que isso causaria problemas porque exigia uma bandeira particular em tempo de compilação que não é recomendado na produção. Além disso, ele não suporta o Python 3 .

O go-python parecia um bom candidato, mas foi escrito para as ligações Python 2.

py parecia muito incompleto, abrangendo apenas algumas das API básicas do Python.

Outros exemplos que eu encontrei usaram o pacote cffi , então o consumo do código não parece um módulo, é apenas uma maneira de chamar os métodos compilados da Go. Também não tem ligações nativas.

Criando bibliotecas compartilhadas em Go

No Go SDK, existe um conjunto de ferramentas chamado cgo , que é uma maneira de importar cabeçalhos C e acessar os tipos e métodos descritos em bibliotecas externas.

Ele também permite que você crie bibliotecas compartilhadas de Go e crie cabeçalhos C automaticamente a partir de seus métodos em Go lang

Tome este pacote realmente simples Go

Até então, execute go build -buildmode=c-shared -o sum.so sum.go

Você verá 3 arquivos, sum.go sum.h and sum.so O arquivo sum.so é o compilado binário e sum.h é o cabeçalho para descrever quais métodos e tipos são definidos dentro do binário.

Para estender o Python escrevendo módulos em C, você precisa começar importando o Python.h e descreva um conjunto de código de placa de caldeira em C. Porque estamos em Go, isso se torna mais complicado e, como os autores anteriores mostraram, requer macros C em Vá, ou muito código Go para envolver os métodos.

Ou não é?

PyBindGen

Muito do trabalho no artigo original de Filippo era sobre as ligações. As ligações são como usar uma API HTTP REST, você precisa conhecer os nomes dos métodos, os parâmetros esperados e as respostas.

PyBindGen , uma ferramenta que tem sido em torno de idades, pode criar ligações do módulo Python para 2 ou 3, com base em um arquivo de cabeçalho C ou C ++. Você pode incluir o código-fonte em seu pacote e até mesmo compilar e incorporar em setuptools.

Como mostrado anteriormente, nós já construímos esse arquivo de cabeçalho do compilador Go.

Ao escrever um script build.py simples, importando PyBindGen, podemos solicitar que ele carregue a sum.h Adicione-o como uma importação e descreva um método no módulo chamado Soma, que leva 2 inteiros, a e b e retorna um valor inteiro.

Chamar build.py irá escrever um módulo Python em C para a tela. Uma vez compilado este módulo, ele importará o binário compilado por go e o oferecerá como um módulo nativo Python. Pipe that to sum.c para criar o código-fonte que envolve nosso binário em um módulo Python. Certifique-se de que você pip install pybindgen primeiro.

python build.py > sum.c

Compilando o módulo

Gcc precisa de mais bandeiras do que os portões das Nações Unidas, então vamos começar a colecionar essas. Os caminhos e as opções para o tempo de execução do Python, no meu caso, o Python 3.6 podem ser obtidos executando python3.6-config --cflags e python3.6-config --ldflags

Em seguida, chame o compilador do GCC para compilar o código C, gerado pelo PyBindGen, que importa os métodos definidos na soma.h, que por sua vez estão no binário sum.so.

gcc sum.c -dynamiclib sum.so -o testsum.so {python-flags}

Importando e testando o módulo

Nosso módulo possui um método único, que cumpre 2 números e retorna um número. Agradável e simples.

(env) bash-3.2$ python
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import testsum
>>> testsum.Sum(1,2)
3

Funciona! Além disso, a help(testsum.Sum) realmente diz-lhe quais são os parâmetros e quais os tipos deles.

Algo um pouco mais complexo

Todos os tutoriais em linha mostram uma soma básica ou função aritmética, o que não é realmente útil. Se você ler a documentação da C-API , você verá que você tem acesso aos tipos comuns de Python.

Uma grande limitação de cgo é que você não pode exportar tipos complexos como struct de uma função, mas quem se importa! Nós temos o tipo PyObject incrível da Python, que pode fazer praticamente qualquer coisa

Desta vez, vamos escrever uma função que retorna um dicionário Python. O tipo de retorno ainda será o ponteiro PyObject, que é o catch-all. Desta vez, precisamos importar os tipos para que o compilador Go saiba o que eles são, isto é do pkg-config (que referenciamos anteriormente no Gcc). Além disso, porque PyUnicode_FromString espera um char * , usamos uma função para converter de uma string Go.

Vamos criar uma chave e valor do tipo str Unicode (isto é o Python 3 depois de tudo).

Vamos testá-lo, precisamos atualizar o arquivo build.py com o novo método e reconstruir a biblioteca

Nós especificamos que o chamado não possui o valor de retorno (ou seja, a memória), mas há mais informações disponíveis na documentação sobre como você deseja manipular a coleta de lixo.

(env) bash-3.2$ python
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import testsum
>>> testsum.NewDictionary()
{'key': 'value'}

Em seguida, por que não ver quais APIs e pacotes estão disponíveis em Go, você pode começar a passar para o Python?

O que você poderia conseguir com a Goroutines? E a API assíncrona que está no Python 3.6? Eu acho que poderia ser possível

E o desempenho?

Eu juntei essa função slap-dash para multiplicar 2 matrizes

Curiosamente, é mais lento em Go / CGo do que em apenas CPython nativo.

Provavelmente fiz algum tipo de erro flagrante aqui. Eu também estou me ajudando com a C-API, então esse código precisa de cheques tipo e cheques NULL-ref, mas a idéia está lá.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *