Risposte:
Dovresti dare un'occhiata a Boost.Python . Ecco la breve introduzione tratta dal loro sito Web:
La libreria Boost Python è un framework per l'interfacciamento di Python e C ++. Ti consente di esporre rapidamente e senza interruzioni funzioni e oggetti delle classi C ++ a Python e viceversa, senza l'uso di strumenti speciali: solo il tuo compilatore C ++. È progettato per avvolgere le interfacce C ++ in modo non intrusivo, quindi non dovresti cambiare il codice C ++ per avvolgerlo, rendendo Boost.Python ideale per esporre librerie di terze parti a Python. L'uso da parte della biblioteca di tecniche avanzate di metaprogrammazione semplifica la sua sintassi per gli utenti, in modo che il wrapping del codice assuma l'aspetto di una sorta di linguaggio di definizione dell'interfaccia dichiarativa (IDL).
Il modulo ctypes fa parte della libreria standard, e quindi è più stabile e ampiamente disponibile rispetto allo swig , che tende sempre a darmi problemi .
Con ctypes, devi soddisfare qualsiasi dipendenza del tempo di compilazione da Python e il tuo binding funzionerà su qualsiasi Python che ha Ctypes, non solo quello su cui è stato compilato.
Supponiamo di avere una semplice classe di esempio C ++ con cui vuoi parlare in un file chiamato foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Poiché i ctypes possono parlare solo con le funzioni C, è necessario fornire quelli che li dichiarano come "C" esterni
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Successivamente devi compilarlo in una libreria condivisa
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
E infine devi scrivere il tuo wrapper Python (ad esempio in 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)
Una volta che hai che puoi chiamarlo come
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(); } }
Il modo più rapido per farlo è usare SWIG .
Esempio dal tutorial SWIG :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
File di interfaccia:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Costruire un modulo Python su 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
Nota che devi avere python-dev. Inoltre in alcuni sistemi i file di intestazione di python saranno in /usr/include/python2.7 in base al modo in cui è stato installato.
Dal tutorial:
SWIG è un compilatore C ++ abbastanza completo con supporto per quasi tutte le funzionalità del linguaggio. Ciò include preelaborazione, puntatori, classi, ereditarietà e persino modelli C ++. SWIG può anche essere utilizzato per impacchettare strutture e classi in classi proxy nella lingua di destinazione, esponendo le funzionalità sottostanti in modo molto naturale.
Ho iniziato il mio viaggio nell'associazione C ++ Python <-> da questa pagina, con l'obiettivo di collegare tipi di dati di alto livello (vettori STL multidimensionali con elenchi Python) :-)
Avendo provato le soluzioni basate su entrambi ctypes e Boost.Python (e non essendo un ingegnere del software) ho trovato loro complesso quando i tipi di dati ad alto livello è richiesto legame, mentre ho trovato SWIG molto più semplice per tali casi.
Questo esempio utilizza quindi SWIG ed è stato testato in Linux (ma SWIG è disponibile ed è ampiamente utilizzato anche in Windows).
L'obiettivo è rendere disponibile una funzione C ++ per Python che prende una matrice sotto forma di un vettore 2D STL e restituisce una media di ogni riga (come un vettore STL 1D).
Il codice in C ++ ("code.cpp") è il seguente:
#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;
}
L'intestazione equivalente ("code.h") è:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Compiliamo innanzitutto il codice C ++ per creare un file oggetto:
g++ -c -fPIC code.cpp
Definiamo quindi un file di definizione dell'interfaccia SWIG ("code.i") per le nostre funzioni 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, generiamo un codice sorgente dell'interfaccia C ++ dal file di definizione dell'interfaccia SWIG.
swig -c++ -python code.i
Finalmente compiliamo il file sorgente dell'interfaccia C ++ generato e colleghiamo tutto insieme per generare una libreria condivisa che è direttamente importabile da Python (le questioni "_"):
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
Ora possiamo usare la funzione negli script 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
C'è anche pybind11
, che è come una versione leggera di Boost.Python e compatibile con tutti i moderni compilatori C ++:
Pytorch
pytorch.org/tutorials/advanced/cpp_extension.html Funziona anche su VS Community
Windows
Dai un'occhiata a pyrex o Cython . Sono linguaggi simili a Python per l'interfacciamento tra C / C ++ e Python.
Per il C ++ moderno, usa cppyy: http://cppyy.readthedocs.io/en/latest/
Si basa su Cling, l'interprete C ++ per Clang / LLVM. I binding sono in fase di esecuzione e non è necessaria alcuna lingua intermedia aggiuntiva. Grazie a Clang, supporta C ++ 17.
Installalo usando pip:
$ pip install cppyy
Per i piccoli progetti, carica semplicemente la libreria pertinente e le intestazioni che ti interessano. Ad esempio, prendi il codice dall'esempio ctypes in questo thread, ma diviso nelle sezioni header e code:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Compilalo:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
e usalo:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
I progetti di grandi dimensioni sono supportati con il caricamento automatico delle informazioni di riflessione preparate e dei frammenti di cmake per crearli, in modo che gli utenti dei pacchetti installati possano semplicemente eseguire:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Grazie a LLVM, sono possibili funzionalità avanzate, come l'istanziazione automatica dei modelli. Per continuare l'esempio:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Nota: sono l'autore di Cppyy.
swig
, ctypes
o boost.python
. Invece di dover scrivere codice per far funzionare python con il tuo codice c ++ ... python fa il duro lavoro per capire c ++. Supponendo che funzioni davvero.
Questo documento, che afferma che Python è tutto ciò di cui uno scienziato ha bisogno , in pratica dice: primo prototipo di tutto in Python. Quindi, quando è necessario velocizzare una parte, utilizzare SWIG e tradurre questa parte in C.
Non l'ho mai usato ma ho sentito cose positive sui ctypes . Se stai provando a usarlo con C ++, assicurati di sfuggire alla modifica del nome tramite extern "C"
. Grazie per il commento, Florian Bösch.
Penso che cffi per Python possa essere un'opzione.
L'obiettivo è chiamare il codice C da Python. Dovresti essere in grado di farlo senza imparare una terza lingua: ogni alternativa richiede di imparare la propria lingua (Cython, SWIG) o API (tipi). Quindi abbiamo cercato di supporre che tu conosca Python e C e minimizzi i bit extra di API che devi imparare.
La domanda è come chiamare una funzione C da Python, se ho capito bene. Quindi la scommessa migliore sono Ctypes (BTW portatile in tutte le varianti di 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
Per una guida dettagliata potresti consultare l'articolo del mio blog .
Uno dei documenti ufficiali di Python contiene dettagli sull'estensione di Python usando C / C ++ . Anche senza l'uso di SWIG , è abbastanza semplice e funziona perfettamente su Windows.
Cython è sicuramente la strada da percorrere, a meno che tu non preveda di scrivere wrapper Java, nel qual caso SWIG potrebbe essere preferibile.
Consiglio di utilizzare l' runcython
utilità della riga di comando, che rende estremamente semplice il processo di utilizzo di Cython. Se devi trasferire dati strutturati in C ++, dai un'occhiata alla libreria di protobuf di Google, è molto conveniente.
Ecco un esempio minimo che ho fatto che utilizza entrambi gli strumenti:
https://github.com/nicodjimenez/python2cpp
Spero che possa essere un utile punto di partenza.
Per prima cosa dovresti decidere qual è il tuo scopo particolare. La documentazione ufficiale di Python sull'estensione e l'incorporamento dell'interprete Python è stata menzionata sopra, posso aggiungere una buona panoramica delle estensioni binarie . I casi d'uso possono essere suddivisi in 3 categorie:
Al fine di dare una prospettiva più ampia per altri interessati e poiché la tua domanda iniziale è un po 'vaga ("per una libreria C o C ++") penso che queste informazioni potrebbero essere interessanti per te. Sul link sopra puoi leggere gli svantaggi dell'uso delle estensioni binarie e delle sue alternative.
Oltre alle altre risposte suggerite, se desideri un modulo acceleratore, puoi provare Numba . Funziona "generando un codice macchina ottimizzato utilizzando l'infrastruttura del compilatore LLVM in fase di importazione, runtime o staticamente (utilizzando lo strumento pycc incluso)".
Adoro cppyy, rende molto facile estendere Python con codice C ++, aumentando notevolmente le prestazioni quando necessario.
È potente e francamente molto semplice da usare,
qui è un esempio di come è possibile creare un array numpy e passarlo a una funzione membro di classe in 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;
}
};
Puoi anche creare un modulo Python molto facilmente (con CMake), in questo modo eviterai di ricompilare sempre il codice C ++.
pybind11 esempio eseguibile minimo
pybind11 è stato precedentemente menzionato su https://stackoverflow.com/a/38542539/895245 ma vorrei fare qui un esempio concreto di utilizzo e qualche ulteriore discussione sull'implementazione.
Tutto sommato, consiglio vivamente pybind11 perché è davvero facile da usare: basta includere un'intestazione e quindi pybind11 usa la magia del modello per ispezionare la classe C ++ che si desidera esporre a Python e lo fa in modo trasparente.
Il rovescio della medaglia di questo modello magico è che rallenta la compilazione aggiungendo immediatamente qualche secondo a qualsiasi file che utilizza pybind11, vedi ad esempio le indagini condotte su questo problema . PyTorch è d'accordo .
Ecco un esempio minimo eseguibile per darti un'idea di quanto sia fantastico pybind11:
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)
Compila ed esegui:
#!/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
Questo esempio mostra come pybind11 ti consente di esporre senza sforzo la ClassTest
classe C ++ a Python! La compilazione produce un file chiamato class_test.cpython-36m-x86_64-linux-gnu.so
che class_test_main.py
raccoglie automaticamente come punto di definizione per il fileclass_test
modulo definito nativamente.
Forse la realizzazione di quanto sia fantastico affonda solo se provi a fare la stessa cosa a mano con l'API nativa Python, vedi ad esempio questo esempio di farlo, che ha circa 10 volte più codice: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c Su quell'esempio puoi vedere come il codice C deve dolorosamente ed esplicitamente definire la classe Python bit per bit con tutte le informazioni che contiene (membri, metodi, ulteriori metadata ...). Guarda anche:
pybind11 afferma di essere simile a quello Boost.Python
che è stato menzionato su https://stackoverflow.com/a/145436/895245 ma più minimale perché è liberato dal gonfiore di essere all'interno del progetto Boost:
pybind11 è una libreria leggera solo intestazione che espone i tipi C ++ in Python e viceversa, principalmente per creare collegamenti Python del codice C ++ esistente. I suoi obiettivi e la sintassi sono simili all'eccellente libreria Boost.Python di David Abrahams: ridurre al minimo il codice della piastra di caldaia nei moduli di estensione tradizionali inferendo le informazioni sul tipo mediante l'introspezione in fase di compilazione.
Il problema principale con Boost.Python — e il motivo della creazione di un progetto simile - è Boost. Boost è un'enorme e complessa suite di librerie di utilità che funziona con quasi tutti i compilatori C ++ esistenti. Questa compatibilità ha il suo costo: trucchi e soluzioni temporanee per il modello arcano sono necessari per supportare i campioni di compilatore più vecchi e più potenti. Ora che i compilatori compatibili con C ++ 11 sono ampiamente disponibili, questo macchinario pesante è diventato una dipendenza eccessivamente grande e non necessaria.
Pensa a questa libreria come una minuscola versione autonoma di Boost.Python con tutto ciò che non è rilevante per la generazione di binding. Senza commenti, i file di intestazione core richiedono solo ~ 4K righe di codice e dipendono da Python (2.7 o 3.x, o PyPy2.7> = 5.7) e dalla libreria standard C ++. Questa implementazione compatta è stata possibile grazie ad alcune delle nuove funzionalità del linguaggio C ++ 11 (in particolare: tuple, funzioni lambda e modelli variadici). Dalla sua creazione, questa libreria è cresciuta oltre Boost.Python in molti modi, portando a un codice di associazione drammaticamente più semplice in molte situazioni comuni.
pybind11 è anche l'unica alternativa non nativa evidenziata dall'attuale documentazione di associazione di Microsoft Python C all'indirizzo: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( archivio ).
Testato su Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.