Respostas:
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 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
extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
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.
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
Há também pybind11
, que é como uma versão leve do Boost.Python e compatível com todos os compiladores C ++ modernos:
Pytorch
pytorch.org/tutorials/advanced/cpp_extension.html Também funciona totalmente no VS Community
Windows
Confira pyrex ou Cython . São linguagens semelhantes ao Python para interface entre C / C ++ e Python.
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.
swig
, ctypes
ou 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.
Este artigo, afirmando que o Python é tudo o que um cientista precisa , basicamente diz: Primeiro protótipo de tudo no Python. Então, quando precisar acelerar uma peça, use SWIG e traduza essa peça para C.
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.
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.
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 .
Um dos documentos oficiais do Python contém detalhes sobre a extensão do Python usando C / C ++ . Mesmo sem o uso do SWIG , é bastante direto e funciona perfeitamente bem no Windows.
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.
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:
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)".
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.
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 ClassTest
classe C ++ ao Python! A compilação produz um arquivo nomeado class_test.cpython-36m-x86_64-linux-gnu.so
que 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.Python
mencionado 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.