Recentemente, li um artigo sobre Habré sobre uma ferramenta muito útil e, como eu procurava algum tipo de projeto há muito tempo para começar a contribuir, decidi ver o que havia no github e como posso ajudar. Um problema era sobre a criação de um invólucro (usarei o invólucro mais tarde) para a biblioteca C. Naquele momento, pensei: "Oh, algo interessante, tenho certeza de que não levará mais que uma hora". Quanto eu estava enganado.
Neste artigo, decidi mostrar não uma maneira de resolver um problema semelhante, mas várias opções diferentes. Mostrarei as opções para criar módulos em Python com compilação em C, usando uma pequena biblioteca auto-escrita C em Python e - a última opção - usando uma grande biblioteca C em Python sem arquivos pain e pxd.
Cython
Os livros já foram escritos sobre isso, existem muitos artigos, incluindo o Habré, então não vou me concentrar muito na instalação ou em algumas coisas básicas. Leia mais aqui
Usando o Cython, podemos resolver vários problemas. Para algumas instâncias do código C em python, ele geralmente se encaixa perfeitamente e resolve parcialmente o problema com as importações da biblioteca.
Vejamos um exemplo simples da documentação oficial.
from __future__ import print_function def fib(n): """Print the Fibonacci series up to n.""" a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a + b print()
Salve este arquivo como fib.pyx
.
.pyx é um formato especial de arquivos Cython, semelhante ao .c para o código C e contém algumas funcionalidades. Também existe .pxd , em C é .he contém uma descrição de funções, estruturas etc.
Para interagir de alguma forma com a função fib, precisamos "compilar" o código. Para fazer isso, crie setup.py
com este conteúdo.
from distutils.core import setup from Cython.Build import cythonize setup( ext_modules=cythonize("fib.pyx"), )
Depois de executar o python3 setup.py build_ext --inplace
você pode importá-lo em python comum e aproveitar a velocidade do trabalho, como em normal linguagens compiladas.
import fib fib.fib(2000)
Mas aqui nós escrevemos código Python e o transformamos em C, mas e quanto a escrever código C e executá-lo em Python?
Não é um problema. Criamos uma nova pasta, por dentro, criamos a pasta lib
na qual criaremos lib/include
e lib/src
, de fato, todos que trabalharam com C já sabem o que estará lá. Na pasta principal, crie outra pasta python_wrap
.
Vamos para lib/include
e create struct.h
, no qual descreveremos uma função e veremos como trabalhar com estruturas em C através do Cython.
typedef struct struct_test{ int a; int b; } struct_test; int minus(struct_test a);
Vamos criar outro arquivo, que chamaremos de include.h
, ele terá outra função e importará a estrutura do struct.h
#include "struct.h" int sum(struct_test param_in_struct);
Agora descreveremos essas funções no arquivo lib/src/test_main.c
#include "include.h" int sum(struct_test param_in_struct){ return param_in_struct.a+param_in_struct.b; } int minus(struct_test param_in_struct){ return param_in_struct.a-param_in_struct.b; }
Sim, não pretendo ser original para nomes de variáveis, mas quase terminamos a parte C. O que mais? Adicione um Makefile, ou melhor, CMake. Na pasta lib
, crie CMakeLists.txt
.
set (TARGET "mal") include_directories( include src ) set (SOURCES ./src/test_main.c ) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") add_library(${TARGET} SHARED ${SOURCES}) target_link_libraries(${TARGET} ${LINKLIBS}) add_library(${TARGET}static STATIC ${SOURCES}) target_link_libraries(${TARGET}static ${LINKLIBS})
No diretório principal, precisamos indicar que temos um projeto para compilar na pasta lib
. Crie outro arquivo CMakeLists.txt
, mas já na raiz.
cmake_minimum_required(VERSION 2.8.2 FATAL_ERROR) cmake_policy(VERSION 2.8) project( TEST ) set (CMAKE_C_FLAGS "-Werror -Wall -Wextra -Wno-unused-parameter -D_GNU_SOURCE -std=c11 -O3 -g ${CMAKE_C_FLAGS}") add_custom_target( ReplicatePythonSourceTree ALL ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ReplicatePythonSourceTree.cmake ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) include( GNUInstallDirs ) add_subdirectory(lib)
Aqui, uso um arquivo pequeno que transfere a estrutura do wrapper Python para o diretório de construção, para que você possa compilar os arquivos Python. Isso pode não ser necessário se você passar caminhos relativos para o diretório de include
e o local onde a biblioteca estará. Por exemplo, se a biblioteca já estiver compilada e instalada no sistema, definiremos os caminhos para os diretórios do sistema, mas mais sobre isso posteriormente.
cmake / ReplicatePythonSourceTree.cmake # Note: when executed in the build dir, then CMAKE_CURRENT_SOURCE_DIR is the # build dir. file( COPY setup.py DESTINATION "${CMAKE_ARGV3}" FILES_MATCHING PATTERN "*.py" ) file( COPY lib/src lib/include DESTINATION "${CMAKE_ARGV3}") file(GLOB MY_WRAP "python_wrap/*" ) file( COPY ${MY_WRAP} DESTINATION "${CMAKE_ARGV3}")
Antes de montar nosso projeto, vamos dar uma olhada na parte do Python. Na pasta python_wrap
criamos dois arquivos main.pxd
e main.pyx
. No main.pxd
precisamos descrever o que temos nos arquivos *.h
.
cdef extern from "include/include.h": ctypedef struct struct_test: int a int b int sum(struct_test param_in_struct); int minus(struct_test param_in_struct);
Usando cdef extern from "include/include.h"
, indicamos qual arquivo descreveremos. A seguir, vem o ctypedef struct struct_test:
descrição da estrutura para que possa ser usada no código Python. No final, de fato, uma descrição de duas funções. Quero observar que precisamos descrever todas as include.h
incluídas em include.h
, se importar uma estrutura e função de outro arquivo de cabeçalho, acreditamos que tudo isso esteja em um arquivo.
Em main.pyx
, escrevemos as funções de transição de Python para C. Isso não é necessário, mas por que carregar código Python com estruturas para C. Para criar uma estrutura, basta definir um dicionário com todos os parâmetros.
from main cimport sum, minus def sum_py(int x, int y): return sum({"a":x,"b":y}) def minus_py(int x, int y): return minus({"a":x,"b":y})
Agora precisamos fazer com que tudo se reúna. Adicione o arquivo setup.py
à raiz do projeto, como fizemos anteriormente.
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension('main', ['main.pyx'], libraries=['mal'], library_dirs=['lib/'])] setup(name = 'work extension module', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules)
Para compilar o código C e compilar nossa biblioteca, criaremos um script bash simples.
Lançamos e verificamos
$ sh build.sh $ python3 > import build.main as main > dir(main) [.... 'minus_py', 'sum_py'] > main.minus_py(10,2) 8 > main.sum_py(10,2) 12
Ctypesgen
O exemplo anterior era muito simples e direto, mas e se você precisar agrupar uma biblioteca muito grande, grave todos os arquivos .pxd com as mãos por um longo período de tempo e seja difícil, portanto, há uma pergunta razoável: o que pode ser usado para automatizar o processo?
git clone https://github.com/davidjamesca/ctypesgen.git
repositório de git clone https://github.com/davidjamesca/ctypesgen.git
. Vá para a biblioteca criada anteriormente build/lib/
e execute o script.
python3 ~/ctypesgen/run.py -lmal ../include/*.h -o main_wrap.py
Depois disso, verificamos o trabalho.
$ python3 > import main_wrap as main > dir(main) [... 'struct_test', 'minus', 'sum'] > main.sum(main.struct_struct_test(1,2)) 3 > main.minus(main.struct_struct_test(1,2)) -1
Bem, voltando à questão das bibliotecas já instaladas, digamos que queremos criar um wrapper em uma biblioteca neon (que já esteja instalada no sistema de qualquer maneira conveniente), conforme mostrado no Leia-me Stypesgen.
$ ctypesgen.py -lneon /usr/local/include/neon/ne_*.h -o neon.py $ python > import neon > dir(neon) [...,'sys', 'time_t', 'union_ne_session_status_info_u', 'wstring_at']
Finalmente, um link para o github , como poderia ser sem ele.