Chiamare C / C ++ da Python?


521

Quale sarebbe il modo più veloce per costruire un'associazione Python a una libreria C o C ++?

(Sto usando Windows se questo è importante.)

Risposte:


170

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).


Boost.Python è una delle librerie più user-friendly in Boost, per una semplice API di chiamata di funzione è abbastanza semplice e fornisce un bollettino che dovresti scrivere tu stesso. È un po 'più complicato se si desidera esporre un'API orientata agli oggetti.
jwfearn,

15
Boost.Python è la cosa peggiore che si possa immaginare. Per ogni nuova macchina e con ogni aggiornamento si accompagna a problemi di collegamento.
Miller,

14
Quasi 11 anni dopo, per una riflessione sulla qualità di questa risposta?
J Evans

4
È ancora l'approccio migliore per interfacciare Python e C ++?
tushaR

8
Forse puoi provare pybind11 che è leggero rispetto a boost.
jdhao,

659

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

14
Questo è praticamente ciò che boost.python fa per te in una singola chiamata di funzione.
Martin Beckett,

203
ctypes è nella libreria standard di Python, swig e boost no. Swig e boost si basano su moduli di estensione e sono quindi legati alle versioni minori di Python che gli oggetti condivisi indipendenti non lo sono. costruire un sorso o aumentare i wrapper può essere una seccatura, i tipi non richiedono requisiti di costruzione.
Florian Bösch,

25
boost si basa sulla magia del modello voodoo e su un sistema di costruzione completamente personalizzato, ctypes si affida alla semplicità. ctypes è dinamico, boost è statico. ctypes può gestire diverse versioni delle librerie. la spinta non può.
Florian Bösch,

32
Su Windows ho dovuto specificare __declspec (dllexport) nelle firme delle mie funzioni affinché Python potesse vederle. Da questo esempio corrisponderebbe a: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald,

13
Non dimenticare di eliminare il puntatore in seguito, ad esempio fornendo una Foo_deletefunzione e chiamandola da un distruttore Python o avvolgendo l'oggetto in una risorsa .
Adversus,

58

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.


50

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

Un'implementazione del caso reale in cui nel codice C ++ i vettori stl vengono passati come riferimenti non const e quindi disponibili da Python come parametri di output: lobianco.org/antonello/personal:portfolio:portopt
Antonello,


30

Dai un'occhiata a pyrex o Cython . Sono linguaggi simili a Python per l'interfacciamento tra C / C ++ e Python.


1
+1 per Cython! Non ho provato CFFI quindi non posso dire quale sia la cosa migliore, ma ho avuto ottime esperienze con Cython - stai ancora scrivendo il codice Python ma puoi usare C in esso. È stato un po 'difficile per me impostare il processo di compilazione con Cython, che in seguito ho spiegato in un post sul blog: martinsosic.com/development/2016/02/08/…
Martinsos,

Potresti voler migliorare la risposta per non essere più solo una risposta di collegamento.
Adelin,

Uso Cython da circa una settimana e mi piace molto: 1) Ho visto i tipi di prodotto in uso ed è BRUTTO e molto soggetto a errori con numerose insidie ​​2) Ti permette di prendere un po 'di codice Python e velocizzarlo dalla sola scrittura statica delle cose 3) È semplice scrivere wrapper Python per metodi e oggetti C / C ++ 4) È ancora ben supportato. Potrebbe avere a che fare con più indicazioni rispetto all'installazione su venvs e alla compilazione incrociata, il che ha richiesto un po 'di tempo per essere risolto. C'è un ottimo video tutorial di 4 ore qui: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason

22

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.


3
In realtà non lo è: Cython è un linguaggio di programmazione simile a Python per scrivere moduli di estensione C per Python (il codice Cython viene tradotto in C, insieme al necessario boilerplate C-API). Fornisce supporto C ++ di base. La programmazione con cppyy comporta solo Python e C ++, senza estensioni di linguaggio. È completamente runtime e non genera codice offline (la generazione pigra si ridimensiona molto meglio). Si rivolge al C ++ moderno (incl. Istanze di template automatiche, mosse, liste di inizializzazione, lambda, ecc. Ecc.) E PyPy è supportato in modo nativo (cioè non attraverso il lento livello di emulazione C-API).
Wim Lavrijsen,

2
Questo documento PyHPC'16 contiene una gamma di numeri di riferimento. Da allora, tuttavia, ci sono stati dei miglioramenti decisivi sul lato CPython.
Wim Lavrijsen,

Mi piace questo approccio, perché non si deve fare lavoro di integrazione supplementare con swig, ctypeso 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.
Trevor Boyd Smith,

cppyy è molto interessante! Vedo nei documenti che viene gestita la ridistribuzione e il preimballaggio. È noto che funziona bene anche con strumenti che impacchettano codice Python (ad esempio, PyInstaller)? E questo è legato al progetto ROOT o fa leva sul suo lavoro?
JimB,

Grazie! Non ho familiarità con PyInstaller, ma i "dizionari" che impacchettano dichiarazioni, percorsi e intestazioni in avanti sono codici C ++ compilati in librerie condivise. Poiché cppyy viene utilizzato per associare il codice C ++, presumo che la gestione di un po 'più di codice C ++ dovrebbe andare bene. E quel codice non dipende dalla C-API di Python (solo il modulo libcppyy lo è), il che semplifica le cose. cppyy stesso può essere installato da conda-forge o pypi (pip), quindi sicuramente tutti questi ambienti funzionano. Sì, cppyy è iniziato come fork da PyROOT, ma da allora è migliorato così tanto che il team ROOT sta rinnovando PyROOT in cima a Cppyy.
Wim Lavrijsen,


15

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.


13

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.

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


2
Penso che questo possa solo chiamare c (non c ++), ancora +1 (mi piace molto il cffi).
Andy Hayden,

8

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 .


Vale la pena notare che mentre i tipi sono portatili, il codice richiede una libreria C specifica di Windows.
Palec,


6

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' runcythonutilità 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.


5

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:

  • moduli acceleratore : per funzionare più velocemente rispetto al codice Python puro equivalente in CPython.
  • moduli wrapper : per esporre interfacce C esistenti al codice Python.
  • accesso al sistema di basso livello : per accedere alle funzionalità di livello inferiore del runtime CPython, del sistema operativo o dell'hardware sottostante.

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)".


3

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 ++.


2

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 ClassTestclasse C ++ a Python! La compilazione produce un file chiamato class_test.cpython-36m-x86_64-linux-gnu.soche class_test_main.pyraccoglie 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.Pythonche è 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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.