Вызов C/C++ из Python?
Какой самый быстрый способ создания привязки Python к библиотеке C или C++?
(Я использую Windows, если это имеет значение.)
14 ответов
Вы должны взглянуть на 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
Существует также pybind11
, которая похожа на облегченную версию Boost.Python и совместима со всеми современными компиляторами C++:
Самый быстрый способ сделать это - использовать 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
Для современного 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.
Эта статья, в которой утверждается, что Python - это все, что нужно ученому, в основном гласит: Первый прототип всего на Python. Затем, когда вам нужно ускорить часть, используйте SWIG и переведите эту часть на C.
Минимальный исполняемый пример pybind11
pybind11 ранее упоминался на /questions/813249/vyizov-cc-iz-python/813282#813282, но я хотел бы привести здесь конкретный пример использования и дальнейшее обсуждение реализации.
В общем, я настоятельно рекомендую 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
изначально определенный модуль.
Возможно, осознание того, насколько это круто, осознано только в том случае, если вы попытаетесь сделать то же самое вручную с помощью собственного API Python, см., Например, этот пример этого, в котором примерно в 10 раз больше кода: https://github.com/cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c В этом примере вы можете увидеть, как код C должен мучительно и явно определять класс Python побитно со всей содержащейся в нем информацией (члены, методы, далее метаданные...). Смотрите также:
- Может ли расширение Python-C++ получить объект C++ и вызвать его функцию-член?
- Предоставление экземпляра класса C++ встроенному интерпретатору Python
- Полный и минимальный пример класса (не метода) с расширением Python C?
- Встраивание Python в C++ и вызов методов из кода C++ с помощью Boost.Python
- Наследование в расширении PythonC++
pybind11 утверждает, что похож на Boost.Python
который был упомянут на /questions/813249/vyizov-cc-iz-python/813284#813284, но более минимален, потому что он освобожден от раздувания внутри проекта Boost:
pybind11 - это облегченная библиотека только для заголовков, которая предоставляет типы C++ в Python и наоборот, в основном для создания привязок Python для существующего кода C++. Ее цели и синтаксис аналогичны превосходной библиотеке Boost.Python Дэвида Абрахамса: минимизировать шаблонный код в традиционных модулях расширения путем определения информации о типе с помощью интроспекции во время компиляции.
Основная проблема Boost.Python и причина создания такого похожего проекта - это Boost. Boost - это чрезвычайно большой и сложный набор служебных библиотек, который работает практически со всеми существующими компиляторами C++. У этой совместимости есть своя цена: для поддержки самых старых и самых ошибочных экземпляров компиляторов необходимы хитрые приемы шаблонов и обходные пути. Теперь, когда компиляторы, совместимые с C++11, широко доступны, эта тяжелая техника превратилась в чрезмерно большую и ненужную зависимость.
Думайте об этой библиотеке как о крошечной автономной версии Boost.Python, в которой удалено все, что не имеет отношения к генерации привязки. Без комментариев основные файлы заголовков требуют только ~4К строк кода и зависят от 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.
Мне нравится cppyy, он позволяет легко расширять Python с помощью кода C++, значительно повышая производительность, когда это необходимо.
Это мощный и, откровенно говоря, очень простой в использовании,
вот пример того, как вы можете создать массив numpy и передать его функции-члену класса в 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++.
Я думаю, что CFFI для Python может быть вариантом.
Цель состоит в том, чтобы вызвать C-код из Python. Вы должны быть в состоянии сделать это, не изучая третий язык: каждая альтернатива требует, чтобы вы изучали их собственный язык (Cython, SWIG) или API (ctypes). Поэтому мы постарались предположить, что вы знаете Python и C, и минимизировать дополнительные биты API, которые вам необходимо изучить.
Я никогда не использовал это, но я слышал хорошие вещи о ctypes. Если вы пытаетесь использовать его с C++, избегайте искажения имени через extern "C"
, Спасибо за комментарий, Флориан Бёш.
Вопрос в том, как вызвать функцию 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 категории:
- Модули акселератора: чтобы работать быстрее, чем эквивалентный чистый код Python, работает в CPython.
- модули-обертки: для представления существующих интерфейсов C в коде Python.
- низкоуровневый доступ к системе: для доступа к низкоуровневым функциям среды выполнения CPython, операционной системы или базового оборудования.
Чтобы дать более широкую перспективу для других заинтересованных лиц, и поскольку ваш первоначальный вопрос немного расплывчатый ("к библиотеке C или C++"), я думаю, что эта информация может быть вам интересна. По ссылке выше вы можете прочитать о недостатках использования бинарных расширений и их альтернатив.
Помимо других предложенных ответов, если вы хотите ускорительный модуль, вы можете попробовать Numba. Он работает "путем генерации оптимизированного машинного кода с использованием инфраструктуры компилятора LLVM во время импорта, во время выполнения или статически (с использованием включенного инструмента pycc)".