Extens˜oes C para Python

Propaganda
Extensões C para Python
Leonardo de Albernaz Ferreira
10 de Junho de 2012
1
1
Introdução
Atualmente contamos com várias linguagens de programação de alto nı́vel, com
uma sintaxe fácil, agradável e métodos de desenvolvimento rápido. Um exemplo
disso é o Python, uma linguagem de tipagem dinâmica que conta com várias
bibliotecas já estabelecidas para solução de problemas numéricos e cientı́ficos.
Pela linguagem ter um alto nı́vel abstração ela acaba com menos performance
de execução do que uma linguagem de baixo nı́vel como o C. Mesmo tendo muitas rotinas otimizadas e com uma performance comparável ao do C podemos
nos deparar com alguns problemas que só podem ser resolvidos com paralelismo e computação distribuı́da. É aı́ que entra a integração do Python com
C, basicamente podemos desenvolver código python que interage com rotinas
C. Dessa forma podemos utilizar de outras tecnologias relativas a computação
de alto desempenho no escopo do C como OpenMP e até CUDA. Construı́mos
uma extensão para o manual e iremos utiliza-la para exemplificar o processo de
desenvolvimento desse tipo de módulo.
2
2
O Módulo Example
A extensão de exemplo utiliza a função de potência de um numero pelo outro
pow(x,y) do C para ilustrar o processo de criação, para discutirmos sua estrutura
e os passos necessários para usa-lo. O diferencial presente nas extensões é o
tráfego de dados entre as linguagens e para isso utilizamos alguns métodos
disponibilizados no header Python.h em nossas funções. Após o desenvolvimento
da nossa extensão teremos que construir e compilar o código já que este se
trata de código C. O processo de compilação e construção é efetuado através de
um script Python que fica responsável pelas chamadas ao compilador e outras
configuradões necessárias.
2.1
Estrutura
A estrutura de um módulo C é composta de 4 partes essenciais: O include do
header Python.h que contem todas as funções e informações necessárias para
efetuar a integração com o python, uma função de inicialização do módulo, um
array usualmente chamado de ”Tabela de Métodos”contendo as informações das
funções da extensão e finalmente as funções propriamente ditas. A extensão que
desenvolvemos para o presente documento é a seguinte:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <Python . h>
s t a t i c PyMethodDef ExampleMethods [ ] = {
{ ”pow\ c ” , pow c , METH VARARGS, ” D e s c r i
o ”} ,
{NULL, NULL, 0 , NULL}
/∗ S e n t i n e l ∗/
};
PyMODINIT FUNC i n i t e x a m p l e m o d u l e ( void ) {
( void ) P y I n i t M o d u l e ( ” examplemodule ” , ExampleMethods ) ;
}
s t a t i c PyObject ∗ pow c ( PyObject ∗ s e l f , PyObject ∗ a r g s ) {
float a ;
float b ;
float resultado ;
PyArg ParseTuple ( a r g s , ” f f ” , &a , &b ) ;
r e s u l t a d o = pow ( a , b ) ;
return P y B u i l d V a l u e ( ” f ” , r e s u l t a d o ) ;
}
Nosso objetivo é utilizar essa extensão como qualquer outro módulo Python,
através de imports e chamadas aos métodos. Vejamos como usaremos nossa
extensão:
1 >>> from examplemodule import ∗
2 >>> pow c ( 1 0 , 2 )
3 100.0
3
2.1.1
Inicialização
Toda extensão C feita para o python possui um método de inicialização. Quando
importamos alguma extensão no python é necessário que ela se inicialize, montando as informações das funções que estão disponı́veis para o usuário. Para
criarmos esse método de inicialização seguimos o padrão de nome que o python
usa para a chamada automática, esse padrão é initnome(), em que nome é o
nome da extensão. A única instrução de código que deverá haver neste método
é o uso da função contida no nosso header do Python, Py InitModule. Esta
função recebe como parâmetros o nome da extensão e um array ”Tabela de
Métodos”com as informações de cada função da nossa extensão, assim ao inicializar o módulo, o python saberá quais os métodos que ele contem.
2.1.2
Tabela de Funções
Como falamos na inicialização, a chamada para a função Py InitModule precisa de um array contendo as informações das nossas funções. Esta tabela é
basicamente um array bi-dimensional da forma abaixo:
1 s t a t i c PyMethodDef ExampleMethods [ ] = {
2
{ ” pow c ” , pow c , METH VARARGS, ” D e s c r i
o ”} ,
3
{NULL, NULL, 0 , NULL}
/∗ S e n t i n e l ∗/
4 };
Como vemos cada linha possui 4 posições. A primeira contem como será o nome
que desejamos chamar no Python para a função no C, a segunda contem o nome
da função no C, a terceira é uma flag que fala para o interpretador que convenção
de chamada usar para a função C, geralmente é ”METH VARARGS”e a quarta
uma descrição da função a tı́tulo de documentação. Geralmente o nome na
primeira posição é exatamente igual ao nome da função do C na segunda, mas
isso não é necessariamente obrigatório. Poderı́amos ter o seguinte caso:
1 s t a t i c PyMethodDef ExampleMethods [ ] = {
2
{ ” p o t e n c i a ” , pow\ c , METH VARARGS, ” D e s c r i
3
{NULL, NULL, 0 , NULL}
/∗ S e n t i n e l ∗/
4 };
o ”} ,
Assim chamarı́amos no Python nossa função da seguinte maneira:
1
2
3
4
>>> from examplemodule import ∗
>>> r e s u l t a d o = p o t e n c i a ( 1 0 , 2 )
>>> r e s u l t a d o
100.0
A nossa tabela de dados referentes as funções tem outra convenção, que é uma
uma linha no final com as posições nulas.
2.1.3
A Função pow c(a,b)
A parte mais importante do nosso módulo de exemplo é a função pow c, ela é o
objetivo da construção da extensão. Suponhamos que a função math.pow(x,y)
4
do python é muito lenta em comparação com a função pow(x,y) do C. Seria útil
termos uma forma de utilizar o pow do C do contexto do Python caso tivéssemos
algum código que necessitasse massivamente da função pow. Então decidimos
que é necessário criar uma extensão para fazer esta integração, mas há alguns
cuidados que devemos tomar: os dados no python estão armazenados de uma
maneira e no C de outra. É por isso que em nossa função pow c(a,b) chamamos
o método ”PyArg ParseTuple(arg, ”ff”, a, b)”ele é responsável por fazer a ponte
entre os tipos de dados do Python e os do C.
2.1.4
PyArg ParseTuple
Esta função possui três argumentos: Variável arg que vem do python contendo todos os dados e variáveis passadas como parâmetro, uma string contendo os tipos dessas variáveis através de uma convensão1 e uma lista de endereços das variáveis declaradas no C deve ser passada. No caso do exemplo
temos a chamada PyArg ParseTuple(arg, ”ff”, a, b), onde passamos a variável
”arg”advinda do python, a String ”ff”que nos indica que existem duas variáveis
do tipo ”Float”na variável arg e os endereços das variáveis de destino a e b. É
possı́vel adaptar diversos tipos de variáveis e até objetos complexos. Vejamos
uma chamada mais complexa:
1
2
3
4
5
int a ;
int b ;
float c ;
Object o b j e c t ;
PyArg ( arg , ” i i f O ” , $a , $b , $c , $ o b j e c t )
Neste caso sabemos através da String que temos dois ints, um float e um objeto. Os objetos são armazenados de uma forma mais complexa e precisam de
tratamento para evitar problemas nos dados, não estaremos falando sobre este
aspecto aqui.
Após termos nossas variáveis no C preenchidas com os valores vindos do
Python podemos então efetuar as manipulações no C de forma padrão. É necessário um cuidado ao usar bibliotecas não nativas do C, pois elas deverão ser
linkadas no processo de empacotamento da extensão.
2.1.5
Py BuildValue
Agora que já traduzimos os dados e efetuamos as transformações com o C temos
que enviar os dados para o Python. Para isso usamos o método Py BuildValue,
ele funciona de uma forma inversa ao PyArg ParseTuple, ele agora vai pegar
nossas variáveis do C e monta-las de forma que o Python consiga interpretar.
Ele recebe a string com a mesma convenção do PyArg ParseTuple e as variáveis
que irão para o Python. Vejamos nossa função pow c:
1 s t a t i c PyObject ∗ pow c ( PyObject ∗ s e l f , PyObject ∗ a r g s ) {
1 http://docs.python.org/c-api/arg.html
5
2
3
4
5
6
7
8
9
10
11
12 }
float a ;
float b ;
float resultado ;
PyArg ParseTuple ( a r g s , ” f f ” , &a , &b ) ;
r e s u l t a d o = pow ( a , b ) ;
return P y B u i l d V a l u e ( ” f ” , r e s u l t a d o ) ;
Pegamos as variáveis a e b do python e aplicamos nela o pow, geramos então
uma terceira variável com o resultado. Este resultado queremos devolver para
o python, para que prossigamos o utilizando no nosso código:
1
2
3
4
>>> from examplemodule import ∗
>>> r e s u l t a d o = pow c ( 1 0 , 2 )
>>> r e s u l t a d o
100.0
Agora fizemos tudo que é necessário fazer para desenvolvermos nosso código.
Podemos prosseguir para o processo de construção e compilação da nossa extensão.
6
3
Construindo e Compilando2
Agora que temos nosso exemplo desenvolvido temos que construir e compilar
a extensão para que possamos importa-lo como qualquer outra extensão. Para
isso utilizamos um script python que se encarrega de chamar o compilador,
linkar as bibliotecas externas e preparar nossa extensão para ser importada no
Python. Esse script por convenção geralmente se chama ”setup.py”e contem o
seguinte formato para nosso exemplo:
1 from d i s t u t i l s . c o r e import s e t u p , E x t e n s i o n
2
3 module1 = E x t e n s i o n ( ’ examplemodule ’ , s o u r c e s = [ ’ examplemodule . c ’ ] )
4
5 s e t u p ( name = ’ Example ’ ,
6
version = ’ 0.01 ’ ,
7
d e s c r i p t i o n = ’ Example ’ ,
8
e x t m o d u l e s = [ module1 ] )
O Script basicamente importa os módulos nativos do Python que se encarregam
de fazer este serviço, nomeia nosso módulo e suas informações na linha 3 explicitando quais os arquivos-fontes que o contemplam e faz a chamada da função
que se encarrega do resto.
3.1
Utilizando bibliotecas C na extensão
Quando utilizamos uma outra biblioteca no C que não seja o Python.h precisamos linka-la junto no processo de construção da extensão, é necessário que
enviemos a biblioteca junto do nosso módulo para quando nós chamarmos as
funções no Python não ocorra problemas de referencia. Vejamos um exemplo
de um módulo que utiliza OpenMP e como ficaria seu Setup.py:
1 from d i s t u t i l s . c o r e import s e t u p , E x t e n s i o n
2 module1 = E x t e n s i o n ( ’ o r b i t S t a t i s t i c s ’ ,
3
s o u r c e s = [ ’ c a l c u l a t i o n s . cpp ’ ] ,
e x t r a c o m p i l e a r g s =[ ’−fopenmp ’ ] ,
4
e x t r a l i n k a r g s =[ ’−lgomp ’ ] )
5
6 s e t u p ( name = ’ O r b i t S t a t i s t i c s C Api ’ ,
7
version = ’ 1.0 ’ ,
8
d e s c r i p t i o n = ’ This i s a a l p h a package ’ ,
9
e x t m o d u l e s = [ module1 ] )
Vemos que agora temos dois parâmetros adicionais na definição da extensão:
extra compile args=[’-fopenmp’] e extra link args=[’-lgomp’]. O Primeiro diz
para o compilador C que o código está utilizando a biblioteca do OpenMP e a
segunda esta dizendo para linkar a biblioteca junto no processo de compilação
e construção.
7
4
Integrando Módulo ao Python
O Script irá gerar um pacote no formato .so e um arquivo no formato .o para o
código fonte. A extensão estará pronta para ser importado como qualquer outra
biblioteca do Python.
8
5
Referências
1. http://docs.python.org/extending/
2. http://docs.python.org/extending/building.html
3. http://starship.python.net/crew/mwh/toext/
9
Download