Antworten:
Sie sollten sich Boost.Python ansehen . Hier ist die kurze Einführung von ihrer Website:
Die Boost Python Library ist ein Framework für die Schnittstelle von Python und C ++. Sie können damit schnell und nahtlos Funktionen und Objekte von C ++ - Klassen für Python verfügbar machen und umgekehrt, ohne spezielle Tools - nur Ihren C ++ - Compiler. Es wurde entwickelt, um C ++ - Schnittstellen nicht aufdringlich zu verpacken, sodass Sie den C ++ - Code überhaupt nicht ändern müssen, um ihn zu verpacken. Boost.Python ist daher ideal, um Bibliotheken von Drittanbietern Python zugänglich zu machen. Die Verwendung fortschrittlicher Metaprogrammiertechniken in der Bibliothek vereinfacht die Syntax für Benutzer, sodass der Wrapping-Code wie eine Art deklarative Interface Definition Language (IDL) aussieht.
Das ctypes- Modul ist Teil der Standardbibliothek und daher stabiler und allgemein verfügbar als swig , was mir immer Probleme bereitete .
Bei ctypes müssen Sie alle Abhängigkeiten der Kompilierungszeit von Python erfüllen, und Ihre Bindung funktioniert für alle Pythons mit ctypes, nicht nur für die, für die sie kompiliert wurden.
Angenommen, Sie haben eine einfache C ++ - Beispielklasse, mit der Sie in einer Datei namens foo.cpp sprechen möchten:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Da ctypes nur mit C-Funktionen kommunizieren können, müssen Sie diejenigen angeben, die sie als externes "C" deklarieren.
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Als nächstes müssen Sie dies zu einer gemeinsam genutzten Bibliothek kompilieren
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
Und schließlich müssen Sie Ihren Python-Wrapper schreiben (z. B. 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)
Sobald Sie das haben, können Sie es wie nennen
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(); } }
Foo_delete
Funktion bereitstellen und sie entweder von einem Python-Destruktor aus aufrufen oder das Objekt in eine Ressource einschließen .
Der schnellste Weg, dies zu tun, ist die Verwendung von SWIG .
Beispiel aus dem SWIG- Tutorial :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
Schnittstellendatei:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Erstellen eines Python-Moduls unter 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
Verwendungszweck:
>>> import example
>>> example.fact(5)
120
Beachten Sie, dass Sie Python-Dev haben müssen. In einigen Systemen befinden sich Python-Header-Dateien je nach Installation in /usr/include/python2.7.
Aus dem Tutorial:
SWIG ist ein ziemlich vollständiger C ++ - Compiler mit Unterstützung für fast alle Sprachfunktionen. Dies umfasst Vorverarbeitung, Zeiger, Klassen, Vererbung und sogar C ++ - Vorlagen. SWIG kann auch verwendet werden, um Strukturen und Klassen in Proxy-Klassen in der Zielsprache zu packen, wodurch die zugrunde liegende Funktionalität auf sehr natürliche Weise verfügbar gemacht wird.
Ich habe meine Reise in der Python <-> C ++ - Bindung von dieser Seite aus mit dem Ziel begonnen, Datentypen auf hoher Ebene (mehrdimensionale STL-Vektoren mit Python-Listen) zu verknüpfen :-)
Nachdem ich die Lösungen ausprobiert habe, die sowohl auf ctypes als auch auf boost.python basieren (und kein Softwareentwickler sind), habe ich sie als komplex empfunden, wenn eine Bindung von Datentypen auf hoher Ebene erforderlich ist, während ich SWIG gefunden habe solchen Fällen als viel einfacher .
In diesem Beispiel wird daher SWIG verwendet, und es wurde unter Linux getestet (aber SWIG ist verfügbar und wird auch unter Windows häufig verwendet).
Ziel ist es, Python eine C ++ - Funktion zur Verfügung zu stellen, die eine Matrix in Form eines 2D-STL-Vektors verwendet und einen Durchschnitt jeder Zeile zurückgibt (als 1D-STL-Vektor).
Der Code in C ++ ("code.cpp") lautet wie folgt:
#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;
}
Der entsprechende Header ("code.h") lautet:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Wir kompilieren zuerst den C ++ - Code, um eine Objektdatei zu erstellen:
g++ -c -fPIC code.cpp
Anschließend definieren wir eine SWIG-Schnittstellendefinitionsdatei ("code.i") für unsere C ++ - Funktionen.
%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"
Mit SWIG generieren wir einen C ++ - Schnittstellenquellcode aus der SWIG-Schnittstellendefinitionsdatei.
swig -c++ -python code.i
Wir kompilieren schließlich die generierte Quelldatei der C ++ - Schnittstelle und verknüpfen alles miteinander, um eine gemeinsam genutzte Bibliothek zu generieren, die direkt von Python importiert werden kann (das "_" ist wichtig):
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
Wir können jetzt die Funktion in Python-Skripten verwenden:
#!/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
Es gibt auch pybind11
eine leichtgewichtige Version von Boost.Python, die mit allen modernen C ++ - Compilern kompatibel ist:
Pytorch
pytorch.org/tutorials/advanced/cpp_extension.html Funktioniert auch vollständig unter VS Community
Windows
Schauen Sie sich Pyrex oder Cython an . Sie sind Python-ähnliche Sprachen für die Schnittstelle zwischen C / C ++ und Python.
Verwenden Sie für modernes C ++ cppyy: http://cppyy.readthedocs.io/en/latest/
Es basiert auf Cling, dem C ++ - Interpreter für Clang / LLVM. Die Bindungen sind zur Laufzeit und es ist keine zusätzliche Zwischensprache erforderlich. Dank Clang unterstützt es C ++ 17.
Installieren Sie es mit pip:
$ pip install cppyy
Laden Sie für kleine Projekte einfach die entsprechende Bibliothek und die Header, an denen Sie interessiert sind. Nehmen Sie beispielsweise den Code aus dem Beispiel ctypes in diesem Thread, teilen Sie ihn jedoch in Header- und Codeabschnitte auf:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Kompilieren Sie es:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
und benutze es:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
Große Projekte werden durch das automatische Laden der vorbereiteten Reflektionsinformationen und der cmake-Fragmente zum Erstellen unterstützt, sodass Benutzer installierter Pakete einfach Folgendes ausführen können:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Dank LLVM sind erweiterte Funktionen wie die automatische Instanziierung von Vorlagen möglich. So setzen Sie das Beispiel fort:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Hinweis: Ich bin der Autor von cppyy.
swig
, ctypes
oder boost.python
. Anstatt dass Sie Code schreiben müssen, damit Python mit Ihrem C ++ - Code funktioniert ... Python macht die harte Arbeit, um C ++ herauszufinden. Vorausgesetzt, es funktioniert tatsächlich.
In diesem Artikel, in dem behauptet wird, Python sei alles, was ein Wissenschaftler braucht , heißt es im Grunde: Erster Prototyp für alles in Python. Wenn Sie dann ein Teil beschleunigen müssen, verwenden Sie SWIG und übersetzen Sie dieses Teil in C.
Ich habe es nie benutzt, aber ich habe gute Dinge über ctypes gehört . Wenn Sie versuchen, es mit C ++ zu verwenden, sollten Sie die Namensverknüpfung über vermeiden extern "C"
. Danke für den Kommentar, Florian Bösch.
Ich denke, CFFI für Python kann eine Option sein.
Ziel ist es, C-Code aus Python aufzurufen. Sie sollten dazu in der Lage sein, ohne eine dritte Sprache zu lernen: Für jede Alternative müssen Sie ihre eigene Sprache (Cython, SWIG) oder API (ctypes) lernen. Daher haben wir versucht anzunehmen, dass Sie Python und C kennen und die zusätzlichen API-Teile, die Sie lernen müssen, minimieren.
Die Frage ist, wie man eine C-Funktion von Python aus aufruft, wenn ich das richtig verstanden habe. Dann sind Ctypes die beste Wahl (BTW portabel für alle Python-Varianten).
>>> 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
Eine ausführliche Anleitung finden Sie in meinem Blog-Artikel .
Eines der offiziellen Python-Dokumente enthält Details zum Erweitern von Python mit C / C ++ . Auch ohne die Verwendung von SWIG ist es recht einfach und funktioniert unter Windows einwandfrei.
Cython ist definitiv der richtige Weg, es sei denn, Sie erwarten das Schreiben von Java-Wrappern. In diesem Fall ist SWIG möglicherweise vorzuziehen.
Ich empfehle die Verwendung des runcython
Befehlszeilenprogramms, da dies die Verwendung von Cython extrem einfach macht. Wenn Sie strukturierte Daten an C ++ übergeben müssen, schauen Sie sich die Protobuf-Bibliothek von Google an. Dies ist sehr praktisch.
Hier sind einige Beispiele, die ich gemacht habe und die beide Tools verwenden:
https://github.com/nicodjimenez/python2cpp
Hoffe, es kann ein nützlicher Ausgangspunkt sein.
Zuerst sollten Sie entscheiden, was Ihr besonderer Zweck ist. Die offizielle Python-Dokumentation zum Erweitern und Einbetten des Python-Interpreters wurde oben erwähnt. Ich kann einen guten Überblick über binäre Erweiterungen hinzufügen . Die Anwendungsfälle können in 3 Kategorien unterteilt werden:
Um anderen Interessierten eine breitere Perspektive zu geben und da Ihre anfängliche Frage etwas vage ist ("zu einer C- oder C ++ - Bibliothek"), denke ich, dass diese Informationen für Sie interessant sein könnten. Unter dem obigen Link können Sie die Nachteile der Verwendung von binären Erweiterungen und deren Alternativen nachlesen.
Abgesehen von den anderen vorgeschlagenen Antworten können Sie Numba ausprobieren, wenn Sie ein Beschleunigermodul wünschen . Es funktioniert "durch Generieren von optimiertem Maschinencode mithilfe der LLVM-Compiler-Infrastruktur zur Importzeit, Laufzeit oder statisch (mithilfe des mitgelieferten pycc-Tools)".
Ich liebe Cppyy, es macht es sehr einfach, Python mit C ++ - Code zu erweitern und die Leistung bei Bedarf dramatisch zu steigern.
Es ist leistungsstark und ehrlich gesagt sehr einfach zu bedienen,
Hier ist ein Beispiel dafür, wie Sie ein Numpy-Array erstellen und an eine Klassenmitgliedsfunktion in C ++ übergeben können.
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;
}
};
Sie können auch sehr einfach ein Python-Modul erstellen (mit CMake). Auf diese Weise vermeiden Sie, dass der C ++ - Code ständig neu kompiliert wird.
pybind11 minimal lauffähiges Beispiel
pybind11 wurde bereits unter https://stackoverflow.com/a/38542539/895245 erwähnt, aber ich möchte hier ein konkretes Anwendungsbeispiel und einige weitere Diskussionen zur Implementierung geben.
Alles in allem empfehle ich pybind11 sehr, da es sehr einfach zu verwenden ist: Sie fügen nur einen Header hinzu und dann verwendet pybind11 Template Magic, um die C ++ - Klasse zu untersuchen, die Sie für Python verfügbar machen möchten, und dies transparent.
Der Nachteil dieser Vorlagenmagie ist, dass sie die Kompilierung verlangsamt und jeder Datei, die pybind11 verwendet, sofort einige Sekunden hinzufügt. Siehe zum Beispiel die Untersuchung zu diesem Problem . PyTorch stimmt zu .
Hier ist ein minimales lauffähiges Beispiel, um Ihnen ein Gefühl dafür zu geben, wie großartig pybind11 ist:
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)
Kompilieren und ausführen:
#!/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
Dieses Beispiel zeigt, wie Sie mit pybind11 die ClassTest
C ++ - Klasse mühelos für Python verfügbar machen können ! Beim Kompilieren wird eine Datei mit dem Namen erstellt, class_test.cpython-36m-x86_64-linux-gnu.so
die class_test_main.py
automatisch als Definitionspunkt für die Datei verwendet wirdclass_test
nativ definierte Modul verwendet wird.
Vielleicht setzt sich die Erkenntnis, wie großartig dies ist, nur dann bemerkbar, wenn Sie versuchen, dasselbe von Hand mit der nativen Python-API zu tun. Sehen Sie sich zum Beispiel dieses Beispiel an, das etwa 10x mehr Code enthält: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c In diesem Beispiel können Sie sehen, wie der C-Code die Python-Klasse Stück für Stück mit allen darin enthaltenen Informationen (Mitgliedern, Methoden usw.) schmerzhaft und explizit definieren muss Metadaten ...). Siehe auch:
pybind11 behauptet, ähnlich zu sein, Boost.Python
wie es unter https://stackoverflow.com/a/145436/895245 erwähnt wurde, aber minimaler, weil es von der Aufblähung befreit ist, innerhalb des Boost-Projekts zu sein:
pybind11 ist eine einfache Nur-Header-Bibliothek, die C ++ - Typen in Python verfügbar macht und umgekehrt, hauptsächlich um Python-Bindungen für vorhandenen C ++ - Code zu erstellen. Seine Ziele und Syntax ähneln der hervorragenden Boost.Python-Bibliothek von David Abrahams: Minimierung des Boilerplate-Codes in herkömmlichen Erweiterungsmodulen durch Ableiten von Typinformationen mithilfe der Introspektion zur Kompilierungszeit.
Das Hauptproblem bei Boost.Python - und der Grund für die Erstellung eines ähnlichen Projekts - ist Boost. Boost ist eine enorm große und komplexe Suite von Dienstprogrammbibliotheken, die mit fast jedem existierenden C ++ - Compiler funktioniert. Diese Kompatibilität hat ihre Kosten: Arkane Template-Tricks und Workarounds sind erforderlich, um die ältesten und fehlerhaftesten Compiler-Exemplare zu unterstützen. Jetzt, da C ++ 11-kompatible Compiler weit verbreitet sind, ist diese schwere Maschinerie zu einer übermäßig großen und unnötigen Abhängigkeit geworden.
Stellen Sie sich diese Bibliothek als eine winzige, in sich geschlossene Version von Boost.Python vor, bei der alles entfernt wurde, was für die Bindungsgenerierung nicht relevant ist. Ohne Kommentare benötigen die Core-Header-Dateien nur ~ 4K Codezeilen und hängen von Python (2.7 oder 3.x oder PyPy2.7> = 5.7) und der C ++ - Standardbibliothek ab. Diese kompakte Implementierung war dank einiger der neuen C ++ 11-Sprachfunktionen möglich (insbesondere: Tupel, Lambda-Funktionen und verschiedene Vorlagen). Seit ihrer Erstellung ist diese Bibliothek in vielerlei Hinsicht über Boost.Python hinaus gewachsen, was in vielen gängigen Situationen zu einem dramatisch einfacheren Bindungscode führt.
pybind11 ist auch die einzige nicht native Alternative, die in der aktuellen Microsoft Python C-Bindungsdokumentation unter https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- hervorgehoben wird. Visual-Studio? view = vs-2019 ( Archiv ).
Getestet unter Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.