Ответы:
Вы должны взглянуть на Boost.Python . Вот краткое введение, взятое с их сайта:
Boost Python Library - это фреймворк для взаимодействия Python и C ++. Он позволяет вам быстро и беспрепятственно представлять функции и объекты классов C ++ для Python и наоборот, не используя специальных инструментов - только ваш компилятор C ++. Он предназначен для незаметного переноса интерфейсов C ++, поэтому вам не нужно вообще менять код C ++, чтобы обернуть его, что делает Boost.Python идеальным для предоставления сторонним библиотекам Python. Использование библиотеки передовых методов метапрограммирования упрощает ее синтаксис для пользователей, так что код переноса выглядит как своего рода декларативный язык определения интерфейса (IDL).
Модуль ctypes является частью стандартной библиотеки и поэтому более стабилен и широко доступен, чем swig , что всегда вызывало у меня проблемы .
В случае ctypes вам необходимо удовлетворить любую зависимость времени компиляции от python, и ваша привязка будет работать на любом питоне, имеющем ctypes, а не только на том, с которым он был скомпилирован.
Предположим, у вас есть простой пример класса C ++, с которым вы хотите поговорить в файле с именем foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Поскольку ctypes может общаться только с функциями C, вам нужно предоставить тех, кто объявляет их как внешние "C"
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Далее вы должны скомпилировать это в общую библиотеку
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
И, наконец, вы должны написать свою оболочку Python (например, в 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)
Если у вас есть это, вы можете назвать это как
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(); } }
Самый быстрый способ сделать это - использовать SWIG .
Пример из учебника SWIG :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
Файл интерфейса:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Сборка модуля Python в 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
Применение:
>>> import example
>>> example.fact(5)
120
Обратите внимание, что у вас должен быть python-dev. Также в некоторых системах заголовочные файлы python будут находиться в /usr/include/python2.7 в зависимости от того, как вы его установили.
Из учебника:
SWIG - это довольно полный компилятор C ++ с поддержкой почти всех языковых функций. Это включает в себя предварительную обработку, указатели, классы, наследование и даже шаблоны C ++. SWIG также можно использовать для упаковки структур и классов в прокси-классы на целевом языке, что очень естественным образом раскрывает базовые функции.
Я начал свое путешествие с привязки Python <-> C ++ с этой страницы с целью связать высокоуровневые типы данных (многомерные векторы STL со списками Python) :-)
Испытав решения, основанные как на ctypes, так и на boost.python (и не будучи инженером-программистом), я обнаружил, что они сложны, когда требуется связывание типов данных высокого уровня, в то время как я обнаружил SWIG для таких случаев гораздо проще.
Таким образом, в этом примере используется SWIG, и он был протестирован в Linux (но SWIG доступен и также широко используется в Windows).
Цель состоит в том, чтобы сделать функцию C ++ доступной для Python, которая принимает матрицу в форме 2D-вектора STL и возвращает среднее значение каждой строки (как 1D-вектор STL).
Код на C ++ ("code.cpp") выглядит следующим образом:
#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;
}
Эквивалентный заголовок ("code.h"):
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Сначала мы скомпилируем код C ++ для создания объектного файла:
g++ -c -fPIC code.cpp
Затем мы определяем файл определения интерфейса SWIG ("code.i") для наших функций 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"
Используя SWIG, мы генерируем исходный код интерфейса C ++ из файла определения интерфейса SWIG.
swig -c++ -python code.i
Наконец, мы скомпилировали сгенерированный исходный файл интерфейса C ++ и соединили все вместе, чтобы создать общую библиотеку, которая напрямую импортируется Python (имеет значение «_»):
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
Теперь мы можем использовать функцию в скриптах 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
Существует также pybind11
, которая похожа на облегченную версию Boost.Python и совместима со всеми современными компиляторами C ++:
Pytorch
pytorch.org/tutorials/advanced/cpp_extension.html Также полностью работает на VS Community
Windows
Для современного C ++ используйте cppyy: http://cppyy.readthedocs.io/en/latest/
Он основан на Cling, интерпретаторе C ++ для Clang / LLVM. Привязки выполняются во время выполнения, и дополнительный промежуточный язык не требуется. Благодаря Clang он поддерживает C ++ 17.
Установите его используя pip:
$ pip install cppyy
Для небольших проектов просто загрузите соответствующую библиотеку и интересующие вас заголовки. Например, возьмите код из примера ctypes, который приведен ниже, но разделите его на разделы заголовка и кода:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Скомпилируйте это:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
и использовать это:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
Большие проекты поддерживаются автоматической загрузкой подготовленной информации об отражении и фрагментов cmake для их создания, так что пользователи установленных пакетов могут просто запустить:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Благодаря LLVM возможны расширенные функции, такие как автоматическое создание шаблона. Чтобы продолжить пример:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Примечание: я автор cppyy.
swig
, ctypes
или boost.python
. Вместо того, чтобы писать код, чтобы заставить python работать с вашим кодом на c ++ ... python делает тяжелую работу, чтобы понять c ++. Предполагая, что это действительно работает.
В этой статье, утверждающей, что Python - это все, что нужно ученому , в основном говорится: Первый прототип всего на Python. Затем, когда вам нужно ускорить часть, используйте SWIG и переведите эту часть на C.
Я никогда не использовал это, но я слышал хорошие вещи о ctypes . Если вы пытаетесь использовать его с C ++, обязательно избегайте искажения имени через extern "C"
. Спасибо за комментарий, Флориан Бёш.
Я думаю, что CFFI для Python может быть вариантом.
Цель состоит в том, чтобы вызвать C-код из Python. Вы должны быть в состоянии сделать это без изучения 3-го языка: каждая альтернатива требует от вас изучения их собственного языка (Cython, SWIG) или API (ctypes). Поэтому мы постарались предположить, что вы знаете Python и C, и минимизировать дополнительные биты API, которые вам необходимо изучить.
Вопрос в том, как вызвать функцию C из Python, если я правильно понял. Тогда лучшим выбором будет Ctypes (кстати, переносимый во всех вариантах 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
Для подробного руководства вы можете обратиться к моей статье в блоге .
Один из официальных документов Python содержит подробности о расширении Python с использованием C / C ++ . Даже без использования SWIG , это довольно просто и отлично работает на Windows.
Определенно, Cython - это путь, если только вы не планируете писать обертки Java, в этом случае SWIG может быть предпочтительнее.
Я рекомендую использовать runcython
утилиту командной строки, она делает процесс использования Cython чрезвычайно простым. Если вам нужно передать структурированные данные в C ++, посмотрите библиотеку Protobuf от Google, это очень удобно.
Вот минимальные примеры, которые я сделал, которые используют оба инструмента:
https://github.com/nicodjimenez/python2cpp
Надеюсь, что это может быть полезной отправной точкой.
Сначала вы должны решить, какова ваша конкретная цель. Официальная документация Python по расширению и внедрению интерпретатора Python была упомянута выше, я могу добавить хороший обзор бинарных расширений . Варианты использования можно разделить на 3 категории:
Чтобы дать более широкую перспективу для других заинтересованных лиц, и поскольку ваш первоначальный вопрос немного расплывчатый («к библиотеке C или C ++»), я думаю, что эта информация может быть вам интересна. По ссылке выше вы можете прочитать о недостатках использования бинарных расширений и их альтернатив.
Помимо других предложенных ответов, если вы хотите ускоритель, вы можете попробовать Numba . Он работает «путем генерации оптимизированного машинного кода с использованием инфраструктуры компилятора LLVM во время импорта, во время выполнения или статически (с помощью включенного инструмента pycc)».
Мне нравится cppyy, он позволяет легко расширять Python с помощью кода C ++, значительно увеличивая производительность при необходимости.
Это мощный и, честно говоря, очень простой в использовании,
здесь приведен пример того, как вы можете создать пустой массив и передать его функции-члену класса в 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;
}
};
Вы также можете очень легко создать модуль Python (с помощью CMake), таким образом вы всегда будете избегать перекомпиляции кода C ++.
Пример минимального запуска pybind11
Ранее pybind11 упоминался по адресу https://stackoverflow.com/a/38542539/895245, но я хотел бы привести здесь конкретный пример использования и дальнейшее обсуждение реализации.
В общем, я настоятельно рекомендую pybind11, потому что он действительно прост в использовании: вы просто включаете заголовок, а затем pybind11 использует магию шаблонов для проверки класса C ++, который вы хотите представить в Python, и делает это прозрачно.
Недостаток этого шаблона в том, что он замедляет компиляцию, сразу добавляя несколько секунд к любому файлу, использующему pybind11, см., Например, исследование, выполненное по этой проблеме . PyTorch соглашается .
Вот минимальный исполняемый пример, чтобы дать вам представление о том, насколько классным является 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)
Скомпилируйте и запустите:
#!/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
В этом примере показано, как pybind11 позволяет без особых усилий представлять ClassTest
класс C ++ для Python! Компиляция создает файл с именем, class_test.cpython-36m-x86_64-linux-gnu.so
который class_test_main.py
автоматически выбирается в качестве точки определения дляclass_test
собственного модуля.
Возможно, осознание того, насколько это круто, только впитывается, если вы попытаетесь сделать то же самое вручную с помощью собственного Python API, посмотрите, например, на этот пример, который имеет примерно в 10 раз больше кода: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c В этом примере вы можете видеть, как код C должен больно и явно определять класс Python побитово со всей информацией, которую он содержит (члены, методы, далее) метаданные ...). Смотрите также:
pybind11 утверждает, что он похож на тот, Boost.Python
который упоминался на https://stackoverflow.com/a/145436/895245, но более минималистичен, потому что он освобожден от раздувания пребывания внутри проекта Boost:
pybind11 - это легковесная библиотека только для заголовков, которая предоставляет типы C ++ в Python и наоборот, в основном для создания привязок Python к существующему коду C ++. Его цели и синтаксис аналогичны отличной библиотеке Boost.Python Дэвида Абрахамса: минимизировать шаблонный код в традиционных модулях расширения, выводя информацию о типах с помощью самоанализа во время компиляции.
Основная проблема с Boost.Python - и причина создания подобного проекта - это Boost. Boost - это чрезвычайно большой и сложный набор служебных библиотек, который работает практически со всеми существующими компиляторами C ++. Эта совместимость имеет свою цену: для поддержки самых старых и самых сложных образцов компилятора необходимы загадочные приемы и обходные пути. Теперь, когда C ++ 11-совместимые компиляторы широко доступны, эта тяжелая машина стала чрезмерно большой и ненужной зависимостью.
Думайте об этой библиотеке как о крошечной автономной версии Boost.Python со всем удаленным, что не имеет отношения к генерации связывания. Без комментариев файлы заголовка ядра требуют только ~ 4K строк кода и зависят от Python (2.7 или 3.x или PyPy2.7> = 5.7) и стандартной библиотеки C ++. Эта компактная реализация стала возможной благодаря некоторым новым возможностям языка C ++ 11 (в частности: кортежам, лямбда-функциям и шаблонам с переменными числами). С момента своего создания эта библиотека вышла за пределы Boost.Python во многих отношениях, что привело к значительному упрощению связывания кода во многих распространенных ситуациях.
pybind11 также является единственной не родной альтернативой, выделенной текущей документацией по связыванию Microsoft Python C по адресу: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( архив ).
Протестировано на Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.