Chamando C / C ++ de Python?


521

Qual seria a maneira mais rápida de construir uma ligação Python a uma biblioteca C ou C ++?

(Estou usando o Windows, se isso importa.)

Respostas:


170

Você deve dar uma olhada no Boost.Python . Aqui está a breve introdução retirada do site:

A Boost Python Library é uma estrutura para interface com Python e C ++. Ele permite que você exponha rápida e perfeitamente as funções e objetos das classes C ++ ao Python e vice-versa, sem usar ferramentas especiais - apenas o seu compilador C ++. Ele foi projetado para quebrar as interfaces C ++ de maneira não invasiva, para que você não precise alterar o código C ++ de forma alguma, para transformá-lo, tornando o Boost.Python ideal para expor bibliotecas de terceiros ao Python. O uso de técnicas avançadas de metaprogramação pela biblioteca simplifica sua sintaxe para os usuários, de modo que o código de quebra de linha assume a aparência de um tipo de linguagem declarativa de definição de interface (IDL).


O Boost.Python é uma das bibliotecas mais amigáveis ​​do Boost, para uma API de chamada de função simples, é bastante direta e fornece um padrão que você precisaria escrever para si mesmo. É um pouco mais complicado se você deseja expor uma API orientada a objetos.
Jwfearn 28/09/08

15
Boost.Python é a pior coisa que se pode imaginar. Para cada nova máquina e a cada atualização, há problemas de conexão.
miller

14
Quase 11 anos depois, tempo para uma reflexão sobre a qualidade dessa resposta?
J Evans

4
Essa ainda é a melhor abordagem para interface de python e c ++?
Tushar

8
Talvez você possa experimentar o pybind11, que é leve comparado ao boost.
Jdhao 16/07/19

659

O módulo ctypes faz parte da biblioteca padrão e, portanto, é mais estável e amplamente disponível do que o swig , que sempre tendia a me causar problemas .

Com ctypes, você precisa satisfazer qualquer dependência de tempo de compilação do python, e sua ligação funcionará em qualquer python que tenha ctypes, não apenas no que foi compilado.

Suponha que você tenha uma classe de exemplo C ++ simples com a qual deseja conversar em um arquivo chamado foo.cpp:

#include <iostream>

class Foo{
    public:
        void bar(){
            std::cout << "Hello" << std::endl;
        }
};

Como os ctypes só podem falar com funções C, você precisa fornecer aqueles que os declaram como "C" externos

extern "C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}

Em seguida, você deve compilar isso em uma biblioteca compartilhada

g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

E, finalmente, você precisa escrever seu wrapper python (por exemplo, em fooWrapper.py)

from ctypes import cdll
lib = cdll.LoadLibrary('./libfoo.so')

class Foo(object):
    def __init__(self):
        self.obj = lib.Foo_new()

    def bar(self):
        lib.Foo_bar(self.obj)

Depois de ter que você pode chamá-lo como

f = Foo()
f.bar() #and you will see "Hello" on the screen

14
Isso é basicamente o que o boost.python faz por você em uma única chamada de função.
276 Martin

203
ctypes está na biblioteca padrão do python, swig e boost não. Swig e boost dependem de módulos de extensão e, portanto, estão vinculados a versões secundárias python que objetos compartilhados independentes não são. Construir um swig ou boost wrappers pode ser uma dor, ctypes não exige requisitos de construção.
Florian Bösch

25
O boost depende da magia do modelo de vodu e de um sistema de criação totalmente personalizado, o ctypes conta com a simplicidade. ctypes é dinâmico, o aumento é estático. ctypes pode lidar com diferentes versões de bibliotecas. impulso não pode.
Florian Bösch

32
No Windows, tive que especificar __declspec (dllexport) em minhas assinaturas de funções para o Python para poder vê-las. Do exemplo acima, isso corresponderia a: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald

13
Não se esqueça de excluir o ponteiro posteriormente fornecendo uma Foo_deletefunção e chamando-a de um destruidor de python ou envolvendo o objeto em um recurso .
Adversus

58

A maneira mais rápida de fazer isso é usando SWIG .

Exemplo do tutorial do SWIG :

/* File : example.c */
int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

Arquivo de interface:

/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}

extern int fact(int n);

Construindo um módulo Python no Unix:

swig -python example.i
gcc -fPIC -c example.c example_wrap.c -I/usr/local/include/python2.7
gcc -shared example.o example_wrap.o -o _example.so

Uso:

>>> import example
>>> example.fact(5)
120

Note que você precisa ter o python-dev. Também em alguns sistemas, os arquivos de cabeçalho python estarão em /usr/include/python2.7, com base na maneira como você o instalou.

Do tutorial:

SWIG é um compilador C ++ bastante completo, com suporte para quase todos os recursos de idioma. Isso inclui pré-processamento, ponteiros, classes, herança e até modelos C ++. O SWIG também pode ser usado para empacotar estruturas e classes em classes proxy no idioma de destino - expondo a funcionalidade subjacente de uma maneira muito natural.


50

Comecei minha jornada na ligação Python <-> C ++ desta página, com o objetivo de vincular tipos de dados de alto nível (vetores STL multidimensionais com listas Python) :-)

Tendo tentado as soluções baseadas em ctypes e boost.python (e não sendo um engenheiro de software), eu as achei complexas quando é necessária a ligação de tipos de dados de alto nível, enquanto eu achei o SWIG muito mais simples para esses casos.

Este exemplo usa, portanto, SWIG e foi testado no Linux (mas o SWIG está disponível e também é amplamente utilizado no Windows).

O objetivo é disponibilizar para o Python uma função C ++ que pega uma matriz na forma de um vetor STL 2D e retorna uma média de cada linha (como um vetor STL 1D).

O código em C ++ ("code.cpp") é o seguinte:

#include <vector>
#include "code.h"

using namespace std;

vector<double> average (vector< vector<double> > i_matrix) {

  // Compute average of each row..
  vector <double> averages;
  for (int r = 0; r < i_matrix.size(); r++){
    double rsum = 0.0;
    double ncols= i_matrix[r].size();
    for (int c = 0; c< i_matrix[r].size(); c++){
      rsum += i_matrix[r][c];
    }
    averages.push_back(rsum/ncols);
  }
  return averages;
}

O cabeçalho equivalente ("code.h") é:

#ifndef _code
#define _code

#include <vector>

std::vector<double> average (std::vector< std::vector<double> > i_matrix);

#endif

Primeiro, compilamos o código C ++ para criar um arquivo de objeto:

g++ -c -fPIC code.cpp

Em seguida, definimos um arquivo de definição de interface SWIG ("code.i") para nossas funções C ++.

%module code
%{
#include "code.h"
%}
%include "std_vector.i"
namespace std {

  /* On a side note, the names VecDouble and VecVecdouble can be changed, but the order of first the inner vector matters! */
  %template(VecDouble) vector<double>;
  %template(VecVecdouble) vector< vector<double> >;
}

%include "code.h"

Usando SWIG, geramos um código fonte da interface C ++ a partir do arquivo de definição da interface SWIG.

swig -c++ -python code.i

Finalmente, compilamos o arquivo fonte da interface C ++ gerada e vinculamos tudo para gerar uma biblioteca compartilhada diretamente importável pelo Python (o "_" é importante):

g++ -c -fPIC code_wrap.cxx  -I/usr/include/python2.7 -I/usr/lib/python2.7
g++ -shared -Wl,-soname,_code.so -o _code.so code.o code_wrap.o

Agora podemos usar a função nos scripts Python:

#!/usr/bin/env python

import code
a= [[3,5,7],[8,10,12]]
print a
b = code.average(a)
print "Assignment done"
print a
print b

Uma implementação caso real onde em vectores stl código de C ++ são passadas como const referências não e, por conseguinte, disponível por pitão como os parâmetros de saída: lobianco.org/antonello/personal:portfolio:portopt
Antonello


30

Confira pyrex ou Cython . São linguagens semelhantes ao Python para interface entre C / C ++ e Python.


1
+1 para Cython! Eu não tentei o cffi, então não posso dizer qual é o melhor, mas tive experiências muito boas com o Cython - você ainda está escrevendo código Python, mas pode usar C nele. Foi um pouco difícil para mim configurar o processo de compilação com o Cython, que expliquei mais tarde em um post do blog: martinsosic.com/development/2016/02/08/…
Martinsos

Você pode melhorar a resposta para não ser mais uma resposta apenas de link.
Adelin

Eu uso o Cython há cerca de uma semana e gosto muito: 1) Vi ctypes em uso e é FEIO e muito propenso a erros com várias armadilhas 2) Permite que você pegue algum código Python e o acelere apenas digitando estaticamente as coisas 3) É fácil escrever wrappers Python para métodos e objetos C / C ++ 4) Ainda é bem suportado. Poderia ter mais orientações em relação à instalação em venvs e compilação cruzada, o que levou um pouco de tempo para se resolver. Há um muito bom vídeo tutorial 4 horas aqui: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason

22

Para C ++ moderno, use cppyy: http://cppyy.readthedocs.io/en/latest/

É baseado no Cling, o intérprete C ++ para Clang / LLVM. As ligações estão no tempo de execução e nenhum idioma intermediário adicional é necessário. Graças ao Clang, ele suporta C ++ 17.

Instale-o usando pip:

    $ pip install cppyy

Para projetos pequenos, basta carregar a biblioteca relevante e os cabeçalhos nos quais você está interessado. Por exemplo, pegue o código do exemplo ctypes neste segmento, mas divida nas seções de cabeçalho e código:

    $ cat foo.h
    class Foo {
    public:
        void bar();
    };

    $ cat foo.cpp
    #include "foo.h"
    #include <iostream>

    void Foo::bar() { std::cout << "Hello" << std::endl; }

Compile-o:

    $ g++ -c -fPIC foo.cpp -o foo.o
    $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

e use-o:

    $ python
    >>> import cppyy
    >>> cppyy.include("foo.h")
    >>> cppyy.load_library("foo")
    >>> from cppyy.gbl import Foo
    >>> f = Foo()
    >>> f.bar()
    Hello
    >>>

Projetos grandes são suportados com o carregamento automático de informações de reflexão preparadas e os fragmentos cmake para criá-los, para que os usuários dos pacotes instalados possam simplesmente executar:

    $ python
    >>> import cppyy
    >>> f = cppyy.gbl.Foo()
    >>> f.bar()
    Hello
    >>>

Graças ao LLVM, são possíveis recursos avançados, como instanciação automática de modelos. Para continuar o exemplo:

    >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
    >>> v.push_back(f)
    >>> len(v)
    1
    >>> v[0].bar()
    Hello
    >>>

Nota: Sou o autor do cppyy.


3
Na verdade, não é: Cython é uma linguagem de programação semelhante a Python para escrever módulos de extensão C para Python (o código Cython é traduzido para C, junto com o clichê necessário da C-API). Ele fornece algum suporte básico a C ++. A programação com o cppyy envolve apenas Python e C ++, sem extensões de idioma. É totalmente em tempo de execução e não gera código offline (a geração lenta é muito melhor). Ele tem como objetivo o C ++ moderno (incluindo instanciações de modelo automáticas, movimentos, listas de inicialização, lambda, etc., etc.) e o PyPy é suportado nativamente (ou seja, não através da lenta camada de emulação da C-API).
Wim Lavrijsen

2
Este documento PyHPC'16 contém uma série de números de referência. Desde então, houve melhorias definitivas no lado do CPython.
Wim Lavrijsen

Eu gosto desta abordagem, porque você não tem que fazer o trabalho de integração adicional com swig, ctypesou boost.python. Em vez de você ter que escrever código para fazer o python funcionar com seu código c ++ ... python faz o trabalho duro para descobrir c ++. Supondo que realmente funcione.
Trevor Boyd Smith

cppyy é muito interessante! Vejo nos documentos que a redistribuição e a pré-embalagem são tratadas. Isso é conhecido por funcionar bem com ferramentas que empacotam código python também (por exemplo, PyInstaller)? E isso está relacionado ao projeto ROOT ou alavanca seu trabalho?
JimB 12/07/19

Obrigado! Não estou familiarizado com o PyInstaller, mas os "dicionários" que empacotam declarações, caminhos e cabeçalhos avançados são códigos C ++ compilados em bibliotecas compartilhadas. Como o cppyy é usado para vincular o código C ++, presumo que manipular um pouco mais de código C ++ seja bom. E esse código não depende da API C do Python (apenas o módulo libcppyy), o que simplifica as coisas. O próprio cppyy pode ser instalado a partir do conda-forge ou do pypi (pip), para que qualquer um desses ambientes funcione, com certeza. Sim, o cppyy começou como um fork do PyROOT, mas desde então melhorou tanto, que a equipe ROOT está reformulando o PyROOT sobre o cppyy.
Wim Lavrijsen 14/07/19


15

Eu nunca o usei, mas ouvi coisas boas sobre ctypes . Se você estiver tentando usá-lo com C ++, não se esqueça de evitar nomes desconectados extern "C". Obrigado pelo comentário, Florian Bösch.


13

Eu acho que o cffi para python pode ser uma opção.

O objetivo é chamar o código C do Python. Você deve conseguir fazer isso sem aprender um terceiro idioma: todas as alternativas exigem que você aprenda seu próprio idioma (Cython, SWIG) ou API (ctypes). Portanto, tentamos assumir que você conhece Python e C e minimizar os bits extras da API que você precisa aprender.

http://cffi.readthedocs.org/en/release-0.7/


2
Eu acho que isso só pode chamar c (não c ++), ainda +1 (eu realmente gosto de cffi).
Andy Hayden

8

A questão é como chamar uma função C do Python, se eu entendi corretamente. Em seguida, a melhor aposta é Ctypes (BTW portátil em todas as variantes do Python).

>>> from ctypes import *
>>> libc = cdll.msvcrt
>>> print libc.time(None)
1438069008
>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19

Para um guia detalhado, você pode consultar o artigo do meu blog .


Pode ser interessante notar que, enquanto os ctypes são portáteis, seu código requer uma biblioteca C específica do Windows.
Palec 28/08/15


6

O Cython é definitivamente o caminho, a menos que você preveja a criação de wrappers Java; nesse caso, o SWIG pode ser preferível.

Eu recomendo usar o runcython utilitário de linha de comando, pois torna extremamente fácil o processo de usar o Cython. Se você precisar passar dados estruturados para C ++, dê uma olhada na biblioteca protobuf do Google, é muito conveniente.

Aqui estão alguns exemplos mínimos que fiz que usam as duas ferramentas:

https://github.com/nicodjimenez/python2cpp

Espero que possa ser um ponto de partida útil.


5

Primeiro você deve decidir qual é o seu objetivo específico. A documentação oficial do Python sobre estender e incorporar o interpretador Python foi mencionada acima. Posso adicionar uma boa visão geral das extensões binárias . Os casos de uso podem ser divididos em 3 categorias:

  • módulos aceleradores : para executar mais rapidamente do que o código Python puro equivalente no CPython.
  • módulos wrapper : para expor as interfaces C existentes ao código Python.
  • acesso ao sistema de baixo nível : para acessar os recursos de nível mais baixo do tempo de execução do CPython, do sistema operacional ou do hardware subjacente.

Para dar uma perspectiva mais ampla a outros interessados ​​e como sua pergunta inicial é um pouco vaga ("para uma biblioteca C ou C ++"), acho que essa informação pode ser interessante para você. No link acima, você pode ler sobre as desvantagens do uso de extensões binárias e suas alternativas.

Além das outras respostas sugeridas, se você deseja um módulo acelerador, pode experimentar o Numba . Ele funciona "gerando código de máquina otimizado usando a infraestrutura do compilador LLVM no momento da importação, tempo de execução ou estaticamente (usando a ferramenta pycc incluída)".


3

Adoro o cppyy, torna muito fácil estender o Python com código C ++, aumentando drasticamente o desempenho quando necessário.

É poderoso e francamente muito simples de usar,

Aqui está um exemplo de como você pode criar uma matriz numpy e passá-la para uma função de membro de classe em C ++.

cppyy_test.py

import cppyy
import numpy as np
cppyy.include('Buffer.h')


s = cppyy.gbl.Buffer()
numpy_array = np.empty(32000, np.float64)
s.get_numpy_array(numpy_array.data, numpy_array.size)
print(numpy_array[:20])

Buffer.h

struct Buffer {
  void get_numpy_array(double *ad, int size) {
    for( long i=0; i < size; i++)
        ad[i]=i;
  }
};

Você também pode criar um módulo Python com muita facilidade (com CMake), dessa forma você evitará recompilar o código C ++ o tempo todo.


2

Exemplo mínimo executável de pybind11

pybind11 foi mencionado anteriormente em https://stackoverflow.com/a/38542539/895245, mas eu gostaria de dar aqui um exemplo concreto de uso e algumas discussões adicionais sobre implementação.

No geral, recomendo o pybind11 porque é realmente fácil de usar: você apenas inclui um cabeçalho e depois o pybind11 usa a mágica do modelo para inspecionar a classe C ++ que deseja expor ao Python e faz isso de forma transparente.

A desvantagem dessa mágica de modelo é que ela diminui a compilação, adicionando imediatamente alguns segundos a qualquer arquivo que usa pybind11; veja, por exemplo, a investigação realizada sobre esse problema . PyTorch concorda .

Aqui está um exemplo mínimo executável para lhe dar uma idéia de como o pybind11 é incrível:

class_test.cpp

#include <string>

#include <pybind11/pybind11.h>

struct ClassTest {
    ClassTest(const std::string &name) : name(name) { }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }
    std::string name;
};

namespace py = pybind11;

PYBIND11_PLUGIN(class_test) {
    py::module m("my_module", "pybind11 example plugin");
    py::class_<ClassTest>(m, "ClassTest")
        .def(py::init<const std::string &>())
        .def("setName", &ClassTest::setName)
        .def("getName", &ClassTest::getName)
        .def_readwrite("name", &ClassTest::name);
    return m.ptr();
}

class_test_main.py

#!/usr/bin/env python3

import class_test

my_class_test = class_test.ClassTest("abc");
print(my_class_test.getName())
my_class_test.setName("012")
print(my_class_test.getName())
assert(my_class_test.getName() == my_class_test.name)

Compile e execute:

#!/usr/bin/env bash
set -eux
g++ `python3-config --cflags` -shared -std=c++11 -fPIC class_test.cpp \
  -o class_test`python3-config --extension-suffix` `python3-config --libs`
./class_test_main.py

Este exemplo mostra como pybind11 permite que você exponha sem esforço a ClassTestclasse C ++ ao Python! A compilação produz um arquivo nomeado class_test.cpython-36m-x86_64-linux-gnu.soque class_test_main.pyé escolhido automaticamente como o ponto de definição para oclass_test módulo definido nativamente.

Talvez a percepção de quão impressionante isso seja importante se você tentar fazer a mesma coisa manualmente com a API Python nativa; veja, por exemplo, este exemplo de fazer isso, que possui cerca de 10x mais código: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c Nesse exemplo, você pode ver como o código C precisa definir de maneira dolorosa e explícita a classe Python, bit a bit, com todas as informações que ele contém (membros, métodos, e ainda mais metadados ...). Veja também:

pybind11 afirma ser semelhante ao Boost.Pythonmencionado em https://stackoverflow.com/a/145436/895245, mas mais mínimo porque é liberado do inchaço de estar dentro do projeto Boost:

pybind11 é uma biblioteca leve apenas de cabeçalho que expõe tipos C ++ em Python e vice-versa, principalmente para criar ligações Python do código C ++ existente. Seus objetivos e sintaxe são semelhantes à excelente biblioteca Boost.Python de David Abrahams: minimizar o código padrão nos módulos de extensão tradicionais, inferindo informações de tipo usando a introspecção em tempo de compilação.

O principal problema do Boost.Python - e o motivo da criação de um projeto semelhante - é o Boost. O Boost é um conjunto enorme e complexo de bibliotecas de utilitários que funciona com quase todos os compiladores C ++ existentes. Essa compatibilidade tem seu custo: truques e soluções alternativas para modelos arcanos são necessários para oferecer suporte às amostras mais antigas e complicadas de compiladores. Agora que os compiladores compatíveis com C ++ 11 estão amplamente disponíveis, esse maquinário pesado tornou-se uma dependência excessivamente grande e desnecessária.

Pense nessa biblioteca como uma pequena versão independente do Boost.Python com tudo o que foi retirado que não é relevante para a geração de ligações. Sem comentários, os arquivos principais do cabeçalho requerem apenas ~ linhas de código 4K e dependem do Python (2.7 ou 3.x ou PyPy2.7> = 5.7) e da biblioteca padrão C ++. Essa implementação compacta foi possível graças a alguns dos novos recursos da linguagem C ++ 11 (especificamente: tuplas, funções lambda e modelos variados). Desde a sua criação, essa biblioteca cresceu além do Boost.Python de várias maneiras, levando a um código de ligação dramaticamente mais simples em muitas situações comuns.

pybind11 também é a única alternativa não nativa destacada pela documentação atual de ligação do Microsoft Python C em: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( arquivo morto ).

Testado no Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.