Eu quero chamar uma biblioteca C de um aplicativo Python. Não quero agrupar a API inteira, apenas as funções e tipos de dados que são relevantes para o meu caso. A meu ver, tenho três opções:
- Crie um módulo de extensão real em C. Provavelmente exagere e eu também gostaria de evitar a sobrecarga de aprender a escrever extensões.
- Use o Cython para expor as partes relevantes da biblioteca C ao Python.
- Faça tudo em Python, usando
ctypes
para se comunicar com a biblioteca externa.
Não tenho certeza se 2) ou 3) é a melhor escolha. A vantagem de 3) é que ctypes
faz parte da biblioteca padrão, e o código resultante seria puro Python - embora eu não tenha certeza do tamanho da vantagem.
Existem mais vantagens / desvantagens em qualquer uma das opções? Qual abordagem você recomenda?
Edit: Obrigado por todas as suas respostas, eles fornecem um bom recurso para quem quer fazer algo semelhante. A decisão, é claro, ainda deve ser tomada para o caso único - não há um tipo de resposta "Esta é a coisa certa". No meu caso, provavelmente vou usar ctypes, mas também estou ansioso para experimentar o Cython em outro projeto.
Como não existe uma única resposta verdadeira, aceitar uma é um tanto arbitrário; Eu escolhi a resposta do FogleBird, pois fornece algumas boas dicas sobre os tipos e, atualmente, também é a resposta mais votada. No entanto, sugiro ler todas as respostas para obter uma boa visão geral.
Obrigado novamente.
Respostas:
ctypes
é a sua melhor aposta para fazê-lo rapidamente, e é um prazer trabalhar enquanto você ainda está escrevendo Python!Recentemente, envolvi um driver FTDI para comunicação com um chip USB usando ctypes e foi ótimo. Eu fiz tudo e trabalhei em menos de um dia de trabalho. (Eu apenas implementei as funções que precisávamos, cerca de 15 funções).
Anteriormente, estávamos usando um módulo de terceiros, PyUSB , para o mesmo objetivo. PyUSB é um módulo de extensão C / Python real. Mas o PyUSB não estava liberando o GIL ao bloquear as leituras / gravações, o que estava causando problemas para nós. Então, eu escrevi nosso próprio módulo usando ctypes, que libera o GIL ao chamar as funções nativas.
Uma coisa a observar é que os ctypes não sabem sobre
#define
constantes e outras coisas na biblioteca que você está usando, apenas as funções, então você terá que redefinir essas constantes em seu próprio código.Aqui está um exemplo de como o código acabou aparecendo (lotes cortados, apenas tentando mostrar a essência dele):
Alguém fez alguns benchmarks nas várias opções.
Eu poderia estar mais hesitante se tivesse que quebrar uma biblioteca C ++ com muitas classes / modelos / etc. Mas o ctypes funciona bem com estruturas e pode até retornar para o Python.
fonte
ctypes
(parapyinotify
), mas gostaria de entender o problema mais detalhadamente.One thing to note is that ctypes won't know about #define constants and stuff in the library you're using, only the functions, so you'll have to redefine those constants in your own code.
Então, eu tenho que definir constantes que estão lá emwinioctl.h
....ctypes
é muito mais lento que a extensão c, já que o gargalo é a interface do Python para o CAviso: a opinião de um desenvolvedor central do Cython à frente.
Eu quase sempre recomendo o Cython sobre ctypes. O motivo é que ele possui um caminho de atualização muito mais suave. Se você usa ctypes, muitas coisas serão simples no início, e certamente é legal escrever seu código FFI em Python simples, sem compilação, compilar dependências e tudo mais. No entanto, em algum momento, você quase certamente descobrirá que precisa chamar muito a sua biblioteca C, seja em loop ou em uma série mais longa de chamadas interdependentes, e gostaria de acelerar isso. É nesse ponto que você notará que não pode fazer isso com ctypes. Ou, quando você precisar de funções de retorno de chamada e descobrir que seu código de retorno de chamada Python se torna um gargalo, você gostaria de acelerá-lo e / ou movê-lo para C também. Novamente, você não pode fazer isso com ctypes.
Com o Cython, OTOH, você é totalmente livre para tornar o código de agrupamento e chamada tão fino ou grosso quanto desejar. Você pode começar com chamadas simples para o seu código C a partir do código Python comum, e o Cython as converterá em chamadas C nativas, sem nenhuma sobrecarga adicional de chamadas e com uma sobrecarga de conversão extremamente baixa para os parâmetros do Python. Quando você notar que precisa de ainda mais desempenho em algum momento em que está fazendo muitas chamadas caras na sua biblioteca C, pode começar a anotar seu código Python ao redor com tipos estáticos e permitir que o Cython o otimize diretamente em C para você. Ou então, você pode começar a reescrever partes do seu código C no Cython, a fim de evitar chamadas e especializar e apertar seus loops algoritmicamente. E se você precisar de um retorno de chamada rápido, basta escrever uma função com a assinatura apropriada e passá-la diretamente para o registro de retorno de chamada C. Novamente, sem sobrecarga, e oferece desempenho simples de chamada em C. E no caso muito menos provável de que você realmente não consiga obter seu código com rapidez suficiente no Cython, você ainda pode reescrever as partes realmente críticas dele em C (ou C ++ ou Fortran) e chamá-lo do seu código Cython de forma natural e nativa. Mas, então, isso realmente se torna o último recurso, em vez da única opção.
Portanto, o ctypes é bom para fazer coisas simples e executar rapidamente algo. No entanto, assim que as coisas começarem a crescer, você provavelmente chegará ao ponto em que perceberá que utilizou melhor o Cython desde o início.
fonte
__radd__
). Isso é especialmente irritante quando você planeja que sua classe interaja com os tipos internos (por exemplo,int
efloat
). Além disso, métodos mágicos em cython são um pouco problemáticos em geral.O Cython é uma ferramenta bastante interessante por si só, vale a pena aprender e é surpreendentemente próxima da sintaxe do Python. Se você faz qualquer computação científica com o Numpy, o Cython é o caminho a seguir, porque ele se integra ao Numpy para operações de matriz rápida.
Cython é um superconjunto da linguagem Python. Você pode lançar qualquer arquivo Python válido, e ele emitirá um programa C válido. Nesse caso, o Cython apenas mapeará as chamadas do Python para a API CPython subjacente. Isso resulta em talvez uma aceleração de 50% porque seu código não é mais interpretado.
Para obter algumas otimizações, você deve começar a contar fatos adicionais ao Cython sobre seu código, como declarações de tipo. Se você disser o suficiente, ele pode reduzir o código para C. puro. Ou seja, um loop for no Python se torna um loop for no C. Aqui você verá ganhos de velocidade maciços. Você também pode vincular a programas externos C aqui.
Usar o código Cython também é incrivelmente fácil. Eu pensei que o manual faz parecer difícil. Você literalmente faz:
e então você pode
import mymodule
no seu código Python e esquecer completamente que ele é compilado até C.De qualquer forma, como o Cython é tão fácil de configurar e começar a usar, sugiro que tente se ele atende às suas necessidades. Não será um desperdício se não for a ferramenta que você está procurando.
fonte
mymod.pyx
módulo e faça,import pyximport; pyximport.install(); import mymod
e a compilação acontece nos bastidores.runcython mymodule.pyx
. E, diferentemente do pyximport, você pode usá-lo para tarefas de vinculação mais exigentes. A única ressalva é que fui eu quem escreveu as 20 linhas do bash e pode ser tendenciosa.Para chamar uma biblioteca C a partir de um aplicativo Python, também existe o cffi, que é uma nova alternativa para ctypes . Traz uma nova aparência para a FFI:
fonte
Vou jogar outro por aí: SWIG
É fácil de aprender, faz muitas coisas certas e suporta muitos mais idiomas, para que o tempo gasto na aprendizagem possa ser bastante útil.
Se você usa o SWIG, está criando um novo módulo de extensão python, mas com o SWIG fazendo a maior parte do trabalho pesado para você.
fonte
Pessoalmente, eu escreveria um módulo de extensão em C. Não se deixe intimidar pelas extensões do Python C - elas não são difíceis de escrever. A documentação é muito clara e útil. Quando escrevi pela primeira vez uma extensão C em Python, acho que levei cerca de uma hora para descobrir como escrever uma - não muito tempo.
fonte
O ctypes é ótimo quando você já possui um blob de biblioteca compilado (como as bibliotecas do sistema operacional). No entanto, a sobrecarga de chamadas é severa; portanto, se você fizer muitas chamadas para a biblioteca e escrever o código C de qualquer maneira (ou pelo menos compilá-lo), eu diria que cython . Não é muito mais trabalhoso, e será muito mais rápido e mais pitônico usar o arquivo pyd resultante.
Pessoalmente, costumo usar o cython para acelerar rapidamente o código python (loops e comparações de números inteiros são duas áreas em que o cython brilha particularmente) e, quando houver algum código / quebra de código envolvido em outras bibliotecas envolvidas, eu voltarei para o Boost. . O Boost.Python pode ser complicado de configurar, mas depois que você o faz funcionar, torna mais fácil a quebra de código C / C ++.
O cython também é ótimo em agrupar numpy (o que aprendi nos procedimentos do SciPy 2009 ), mas como não usei o numpy, não posso comentar sobre isso.
fonte
Se você já tem uma biblioteca com uma API definida, acho que
ctypes
é a melhor opção, pois você só precisa fazer uma pequena inicialização e depois chamar mais ou menos a biblioteca do jeito que está acostumado.Acho que o Cython ou a criação de um módulo de extensão em C (o que não é muito difícil) são mais úteis quando você precisa de um novo código, por exemplo, chamar essa biblioteca e executar algumas tarefas complexas e demoradas, e depois passar o resultado para o Python.
Outra abordagem, para programas simples, é executar diretamente um processo diferente (compilado externamente), produzindo o resultado na saída padrão e chamá-lo com o módulo de subprocesso. Às vezes, é a abordagem mais fácil.
Por exemplo, se você criar um programa de console C que funcione mais ou menos dessa maneira
Você poderia chamá-lo de Python
Com um pouco de formatação de sequência, você pode obter o resultado da maneira que desejar. Você também pode capturar a saída de erro padrão, por isso é bastante flexível.
fonte
shell=True
pode facilmente resultar em algum tipo de exploração quando um usuário realmente obtém um shell. Tudo bem quando o desenvolvedor é o único usuário, mas no mundo todo há um monte de truques irritantes esperando por algo assim.Há um problema que me fez usar ctypes e não cython e que não é mencionado em outras respostas.
Usando ctypes, o resultado não depende do compilador que você está usando. Você pode escrever uma biblioteca usando mais ou menos qualquer idioma que possa ser compilado na biblioteca compartilhada nativa. Não importa muito, qual sistema, qual idioma e qual compilador. Cython, no entanto, é limitado pela infraestrutura. Por exemplo, se você deseja usar o compilador Intel no Windows, é muito mais complicado fazer o cython funcionar: você deve "explicar" o compilador para o cython, recompilar algo com esse compilador exato etc. O que limita significativamente a portabilidade.
fonte
Se você estiver direcionando o Windows e optar por agrupar algumas bibliotecas proprietárias do C ++, poderá descobrir em breve que versões diferentes do
msvcrt***.dll
(Visual C ++ Runtime) são ligeiramente incompatíveis.Isso significa que você pode não conseguir usar,
Cython
pois o resultadowrapper.pyd
está vinculadomsvcr90.dll
(Python 2.7) oumsvcr100.dll
(Python 3.x) . Se a biblioteca que você está agrupando estiver vinculada a uma versão diferente do tempo de execução, você estará sem sorte.Em seguida, para que as coisas funcionem, você precisará criar wrappers C para bibliotecas C ++, vincule a dll do wrapper à mesma versão da
msvcrt***.dll
sua biblioteca C ++. E, em seguida, usectypes
para carregar sua DLL de invólucro enrolado à mão dinamicamente no tempo de execução.Portanto, há muitos detalhes pequenos, que são descritos em detalhes no seguinte artigo:
"Bibliotecas nativas bonitas (em Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
fonte
Há também uma possibilidade de usar o GObject Introspection para bibliotecas que usam GLib .
fonte
Eu sei que essa é uma pergunta antiga, mas essa coisa aparece no google quando você pesquisa coisas do tipo
ctypes vs cython
, e a maioria das respostas aqui são escritas por aqueles que já são proficientescython
ouc
que podem não refletir o tempo real que você precisou investir para aprendê-las. para implementar sua solução. Eu sou um novato completo em ambos. Nunca toqueicython
antes e tenho muito pouca experiênciac/c++
.Nos últimos dois dias, eu estava procurando uma maneira de delegar uma parte com alto desempenho do meu código para algo mais baixo que o python. Eu implementei meu código em
ctypes
eCython
, que consistia basicamente em duas funções simples.Eu tinha uma lista enorme de cordas que precisava ser processada. Aviso
list
estring
. Ambos os tipos não correspondem perfeitamente aos tiposc
, porque as strings python são por padrão unicode e asc
strings não. Listas em python simplesmente NÃO são matrizes de c.Aqui está o meu veredicto. Use
cython
. Ele se integra mais fluentemente ao python e é mais fácil trabalhar com isso em geral. Quando algo der errado, o resultado éctypes
apenas um erro de execução, pelo menoscython
você fornecerá avisos de compilação com um rastreamento de pilha sempre que possível, e você poderá retornar facilmente um objeto python válidocython
.Aqui está uma descrição detalhada de quanto tempo eu precisei investir em ambos para implementar a mesma função. Fiz muito pouca programação C / C ++ a propósito:
Ctypes:
c
código funciona.c
código.c
código na base de código real e vi quectypes
não funciona bem com omultiprocessing
módulo, pois seu manipulador não é selecionável por padrão.multiprocessing
módulo e tentei novamente.c
código gerou segfaults na minha base de código, embora tenha passado no meu código de teste. Bem, isso provavelmente é minha culpa por não ter verificado bem com casos extremos, estava procurando uma solução rápida.c
código e, na segunda ou terceira iteração do loop python que o usa, euUnicodeError
não decodificava um byte em alguma posição, embora codificasse e decodificasse tudo explicitamente.Nesse ponto, decidi procurar uma alternativa e resolvi procurar
cython
:setuptools
vez dedistutils
.setup.py
para usar o módulo compilado na minha base de código.multiprocessing
versão do codebase. Funciona.Para o registro, é claro, não medi os horários exatos do meu investimento. Pode muito bem ser que minha percepção do tempo tenha sido um pouco atenta devido ao esforço mental necessário enquanto eu estava lidando com tipos. Mas deve transmitir a sensação de lidar com
cython
ectypes
fonte