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