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.