Odpowiedzi:
Powinieneś spojrzeć na Boost.Python . Oto krótkie wprowadzenie z ich strony internetowej:
Biblioteka Boost Python jest strukturą dla interfejsów Python i C ++. Pozwala szybko i bezproblemowo udostępniać funkcje i obiekty klas C ++ w Pythonie i odwrotnie, bez użycia specjalnych narzędzi - tylko kompilatora C ++. Został zaprojektowany do nieinwazyjnego pakowania interfejsów C ++, dzięki czemu nie trzeba wcale zmieniać kodu C ++, aby go zawinąć, dzięki czemu Boost.Python jest idealny do udostępniania bibliotek innych firm w Pythonie. Zastosowanie przez bibliotekę zaawansowanych technik metaprogramowania upraszcza składnię dla użytkowników, dzięki czemu zawijanie kodu przypomina wygląd deklaratywnego języka definicji interfejsu (IDL).
Moduł ctypes jest częścią standardowej biblioteki, a zatem jest bardziej stabilny i szeroko dostępny niż swig , co zawsze sprawiało mi problemy .
W przypadku ctypes musisz spełnić każdą zależność czasową kompilacji od Pythona, a twoje wiązanie będzie działać na każdym pythonie, który ma ctypy, a nie tylko na tym, z którym został skompilowany.
Załóżmy, że masz prostą przykładową klasę C ++, z którą chcesz rozmawiać w pliku o nazwie foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Ponieważ ctypy mogą rozmawiać tylko z funkcjami języka C, musisz podać te, które deklarują je jako zewnętrzne „C”
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Następnie musisz skompilować to do biblioteki współdzielonej
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
I na koniec musisz napisać swoje opakowanie Pythona (np. W 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)
Gdy już to zrobisz, możesz to tak nazwać
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(); } }
Najszybszym sposobem na to jest użycie SWIG .
Przykład z samouczka SWIG :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
Plik interfejsu:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Budowanie modułu Python na Uniksie:
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
Stosowanie:
>>> import example
>>> example.fact(5)
120
Zauważ, że musisz mieć python-dev. Również w niektórych systemach pliki nagłówkowe Pythona będą w /usr/include/python2.7 w zależności od sposobu zainstalowania.
Z samouczka:
SWIG to dość kompletny kompilator C ++ z obsługą prawie wszystkich funkcji językowych. Obejmuje to przetwarzanie wstępne, wskaźniki, klasy, dziedziczenie, a nawet szablony C ++. SWIG może być również używany do pakowania struktur i klas w klasy proxy w języku docelowym - odsłaniając podstawową funkcjonalność w bardzo naturalny sposób.
Swoją podróż rozpocząłem w powiązaniu Pythona <-> C ++ z tej strony, mając na celu połączenie typów danych wysokiego poziomu (wielowymiarowe wektory STL z listami Pythona) :-)
Po wypróbowaniu rozwiązań opartych na obu typach i boost.python (i nie będąc inżynierem oprogramowania) Znalazłem je złożone typy danych, gdy wymagany jest wysoki poziom wiązania, natomiast znalazłem haust znacznie prostsze dla takich przypadków.
W tym przykładzie użyto zatem SWIG i został on przetestowany w systemie Linux (ale SWIG jest dostępny i jest również powszechnie używany w systemie Windows).
Celem jest udostępnienie Pythonowi funkcji C ++, która przyjmuje macierz w postaci wektora 2D STL i zwraca średnią każdego wiersza (jako wektor 1D STL).
Kod w C ++ („code.cpp”) jest następujący:
#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;
}
Odpowiednik nagłówka („code.h”) to:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Najpierw kompilujemy kod C ++, aby utworzyć plik obiektowy:
g++ -c -fPIC code.cpp
Następnie definiujemy plik definicji interfejsu SWIG („code.i”) dla naszych funkcji 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"
Za pomocą SWIG generujemy kod źródłowy interfejsu C ++ z pliku definicji interfejsu SWIG.
swig -c++ -python code.i
W końcu kompilujemy wygenerowany plik źródłowy interfejsu C ++ i łączymy wszystko razem, aby wygenerować bibliotekę współdzieloną, którą można bezpośrednio importować przez Python (ważne jest „_”):
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
Możemy teraz używać tej funkcji w skryptach 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
Jest też pybind11
, która jest jak lekka wersja Boost.Python i kompatybilna ze wszystkimi nowoczesnymi kompilatorami C ++:
Pytorch
Pytorch.org/tutorials/advanced/cpp_extension.html Działa również w pełni w VS Community
systemie Windows
Dla współczesnego C ++ użyj cppyy: http://cppyy.readthedocs.io/en/latest/
Opiera się na Cling, interpretatorze C ++ dla Clang / LLVM. Wiązania są w czasie wykonywania i nie jest wymagany żaden dodatkowy język pośredni. Dzięki Clang obsługuje C ++ 17.
Zainstaluj go za pomocą pip:
$ pip install cppyy
W przypadku małych projektów wystarczy załadować odpowiednią bibliotekę i nagłówki, które Cię interesują. Np. Weź kod z przykładu ctypes to ten wątek, ale podzielony na sekcje nagłówka i kodu:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Skompiluj to:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
i użyj go:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
Duże projekty są obsługiwane przez automatyczne ładowanie przygotowanych informacji o odbiciu i fragmentów cmake do ich utworzenia, aby użytkownicy zainstalowanych pakietów mogli po prostu uruchomić:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Dzięki LLVM możliwe są zaawansowane funkcje, takie jak automatyczne tworzenie szablonów. Aby kontynuować przykład:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Uwaga: Jestem autorem cppyy.
swig
, ctypes
lub boost.python
. Zamiast pisać kod, aby Python działał z Twoim kodem c ++ ... Python wykonuje ciężką pracę, aby znaleźć c ++. Zakładając, że to faktycznie działa.
Ten artykuł, w którym twierdzi się, że Python jest wszystkim, czego potrzebuje naukowiec , w zasadzie mówi: Najpierw prototypuj wszystko w Pythonie. Następnie, gdy musisz przyspieszyć część, użyj SWIG i przetłumacz tę część na C.
Nigdy go nie używałem, ale słyszałem dobre rzeczy o typach . Jeśli próbujesz używać go z C ++, pamiętaj, aby uniknąć przekłamywania nazw przez extern "C"
. Dzięki za komentarz, Florian Bösch.
Myślę, że cffi dla Pythona może być opcją.
Celem jest wywołanie kodu C z Pythona. Powinieneś być w stanie to zrobić bez uczenia się trzeciego języka: każda alternatywa wymaga nauki własnego języka (Cython, SWIG) lub API (ctyp). Próbowaliśmy więc założyć, że znasz Python i C i zminimalizować dodatkowe bity API, których musisz się nauczyć.
Pytanie brzmi: jak wywołać funkcję C z Pythona, jeśli dobrze zrozumiałem. Zatem najlepszym wyborem są typy Ctyp (BTW przenośne we wszystkich wariantach Pythona).
>>> 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
Szczegółowy przewodnik możesz znaleźć w moim blogu .
Jeden z oficjalnych dokumentów Pythona zawiera szczegółowe informacje na temat rozszerzania Pythona za pomocą C / C ++ . Nawet bez użycia SWIG jest dość prosty i działa doskonale w systemie Windows.
Cython jest zdecydowanie właściwą drogą, chyba że spodziewasz się pisać opakowania Java, w takim przypadku SWIG może być lepszym rozwiązaniem.
Polecam korzystanie z runcython
narzędzia wiersza poleceń, dzięki czemu proces korzystania z Cython jest niezwykle łatwy. Jeśli potrzebujesz przekazać dane strukturalne do C ++, spójrz na bibliotekę protobuf Google, jest to bardzo wygodne.
Oto minimalne przykłady, które stworzyłem przy użyciu obu narzędzi:
https://github.com/nicodjimenez/python2cpp
Mam nadzieję, że może to być przydatny punkt wyjścia.
Najpierw powinieneś zdecydować, jaki jest twój konkretny cel. Oficjalna dokumentacja Pythona dotycząca rozszerzania i osadzania interpretera Pythona została wspomniana powyżej, mogę dodać dobry przegląd rozszerzeń binarnych . Przypadki użycia można podzielić na 3 kategorie:
Aby dać szerszą perspektywę innym zainteresowanym, a ponieważ twoje początkowe pytanie jest nieco niejasne („do biblioteki C lub C ++”), myślę, że ta informacja może być dla ciebie interesująca. Na powyższym linku możesz przeczytać o wadach korzystania z rozszerzeń binarnych i ich alternatyw.
Oprócz innych sugerowanych odpowiedzi, jeśli chcesz moduł akceleratora, możesz wypróbować Numba . Działa „generując zoptymalizowany kod maszynowy przy użyciu infrastruktury kompilatora LLVM w czasie importu, w czasie wykonywania lub statycznie (przy użyciu dołączonego narzędzia pycc)”.
Uwielbiam cppyy, bardzo ułatwia rozszerzenie Pythona za pomocą kodu C ++, dramatycznie zwiększając wydajność w razie potrzeby.
Jest potężny i szczerze mówiąc bardzo prosty w użyciu,
tutaj jest przykład, w jaki sposób można utworzyć tablicę numpy i przekazać ją do funkcji członka klasy w 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])
Bufor. H
struct Buffer {
void get_numpy_array(double *ad, int size) {
for( long i=0; i < size; i++)
ad[i]=i;
}
};
Możesz także bardzo łatwo stworzyć moduł Pythona (z CMake), dzięki czemu unikniesz ponownej kompilacji kodu C ++ przez cały czas.
pybind11 minimalny możliwy do uruchomienia przykład
pybind11 był wcześniej wspomniany na https://stackoverflow.com/a/38542539/895245, ale chciałbym podać tutaj konkretny przykład użycia i trochę dalszej dyskusji na temat implementacji.
W sumie gorąco polecam pybind11, ponieważ jest naprawdę łatwy w użyciu: wystarczy dołączyć nagłówek, a następnie pybind11 używa magii szablonów, aby sprawdzić klasę C ++, którą chcesz udostępnić Pythonowi i robi to w przejrzysty sposób.
Minusem tej magii szablonu jest to, że spowalnia kompilację, natychmiast dodając kilka sekund do dowolnego pliku korzystającego z pybind11, patrz na przykład dochodzenie przeprowadzone w tej sprawie . PyTorch zgadza się .
Oto minimalny przykład, który można uruchomić, aby poczuć, jak niesamowity jest 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)
Skompiluj i uruchom:
#!/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
Ten przykład pokazuje, jak pybind11 pozwala bez wysiłku wystawiać ClassTest
klasę C ++ na Pythona! Kompilacja tworzy plik o nazwie, class_test.cpython-36m-x86_64-linux-gnu.so
który class_test_main.py
automatycznie wybiera się jako punkt definicji plikuclass_test
natywnie zdefiniowanego modułu.
Być może uświadomienie sobie, jak to jest niesamowite, zatapia się tylko wtedy, gdy próbujesz zrobić to samo ręcznie z natywnym API Pythona, zobacz na przykład ten przykład wykonania, który zawiera około 10 razy więcej kodu: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c W tym przykładzie możesz zobaczyć, jak kod C musi boleśnie i wyraźnie definiować klasę Python krok po kroku ze wszystkimi zawartymi w niej informacjami (członkowie, metody, dalej metadane ...). Zobacz też:
pybind11 twierdzi, że jest podobny do Boost.Python
tego, o którym wspomniano na https://stackoverflow.com/a/145436/895245, ale bardziej minimalny, ponieważ jest wolny od nadęty bycia w projekcie Boost:
pybind11 to lekka biblioteka zawierająca tylko nagłówki, która eksponuje typy C ++ w Pythonie i odwrotnie, głównie w celu tworzenia powiązań Pythona z istniejącym kodem C ++. Jego cele i składnia są podobne do doskonałej biblioteki Boost.Python autorstwa Davida Abrahamsa: aby zminimalizować kod płyty wzorcowej w tradycyjnych modułach rozszerzeń poprzez wywnioskowanie informacji o typie przy użyciu introspekcji w czasie kompilacji.
Główny problem z Boost.Python - i powód stworzenia takiego podobnego projektu - to Boost. Boost to niezwykle duży i złożony zestaw bibliotek narzędziowych, który działa z prawie każdym istniejącym kompilatorem C ++. Ta kompatybilność ma swój koszt: tajemnicze sztuczki związane z szablonami i obejścia są niezbędne do obsługi najstarszych i najbardziej szkodliwych próbek kompilatora. Teraz, gdy kompilatory kompatybilne z C ++ 11 są powszechnie dostępne, ta ciężka maszyna stała się zbyt dużą i niepotrzebną zależnością.
Pomyśl o tej bibliotece jako o małej, samodzielnej wersji Boost.Python ze wszystkim, co zostało usunięte, co nie ma znaczenia dla generowania powiązań. Bez komentarzy podstawowe pliki nagłówkowe wymagają tylko ~ 4 wierszy kodu i zależą od Pythona (2.7 lub 3.x lub PyPy2.7> = 5.7) i standardowej biblioteki C ++. Ta kompaktowa implementacja była możliwa dzięki niektórym nowym funkcjom języka C ++ 11 (w szczególności: krotki, funkcje lambda i szablony variadic). Od momentu powstania biblioteka ta rozszerzyła się na wiele sposobów poza Boost.Python, prowadząc do znacznie prostszego wiązania kodu w wielu typowych sytuacjach.
pybind11 jest także jedyną nie-rodzimą alternatywą wyróżnioną przez aktualną dokumentację wiązania Microsoft Python C pod adresem : https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( archiwum ).
Testowane na Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.