




Самый быстрый способ сделать это - использовать 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 is a fairly complete C++ compiler with support for nearly every language feature. This includes preprocessing, pointers, classes, inheritance, and even C++ templates. SWIG can also be used to package structures and classes into proxy classes in the target language — exposing the underlying functionality in a very natural manner.
Вам стоит взглянуть на Boost.Python. Вот краткое введение с их веб-сайта:
The Boost Python Library is a framework for interfacing Python and C++. It allows you to quickly and seamlessly expose C++ classes functions and objects to Python, and vice-versa, using no special tools -- just your C++ compiler. It is designed to wrap C++ interfaces non-intrusively, so that you should not have to change the C++ code at all in order to wrap it, making Boost.Python ideal for exposing 3rd-party libraries to Python. The library's use of advanced metaprogramming techniques simplifies its syntax for users, so that wrapping code takes on the look of a kind of declarative interface definition language (IDL).
Boost.Python - худшее, что можно вообразить. С каждой новой машиной и с каждым обновлением возникают проблемы со связью.
Спустя почти 11 лет пора задуматься о качестве этого ответа?
Это все еще лучший подход к интерфейсу python и C++?
Возможно, вы можете попробовать pybind11, который легче по сравнению с boost.
Модуль ctypes является частью стандартной библиотеки и поэтому более стабилен и широко доступен, чем глоток, который всегда давал мне проблемы.
С ctypes вам необходимо удовлетворить любую зависимость времени компиляции от python, и ваша привязка будет работать на любом питоне, имеющем ctypes, а не только на том, для которого он был скомпилирован.
Предположим, у вас есть простой пример класса C++, с которым вы хотите поговорить, в файле с именем foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Поскольку ctypes могут взаимодействовать только с функциями C, вам необходимо предоставить те, которые объявляют их как extern "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
Это в значительной степени то, что делает за вас boost.python за один вызов функции.
ctypes находится в стандартной библиотеке python, swig и boost - нет. Swig и boost полагаются на модули расширения и поэтому привязаны к второстепенным версиям Python, а независимые общие объекты - нет. создание обертки swig или boost может быть проблемой, ctypes не требует сборки.
boost полагается на магию шаблонов voodoo и полностью настраиваемую систему сборки, ctypes полагается на простоту. ctypes - динамический, boost - статический. ctypes могут обрабатывать разные версии библиотек. буст не может.
mgb: но вы меня разозлили из-за буста, я бы посоветовал вам написать ответ самостоятельно. Однако, чтобы продемонстрировать превосходство над ctypes, это должен быть тот же пример, менее 4 строк кода оболочки C++, менее 2 строк инструкций по сборке и никаких строк python, ну и поместите его на один экран.
Договорились: у меня тоже проблемы с глотком и наддувом. Вы должны четко указать в своем ответе, какие это файлы и какое имя им нужно получить.
В Windows мне пришлось указать __declspec (dllexport) в сигнатурах моих функций, чтобы Python мог их видеть. В приведенном выше примере это будет соответствовать: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
А как насчет передачи аргументов в bar ()? Возможно ли это сделать? Например, предположим, что вы хотите передать python dict в bar (), а bar () выполнить итерацию и распечатать каждый элемент.
Взгляните на ссылку ctypes, указанную в ответе. В основном вам нужно вручную преобразовать все аргументы в основные типы, известные C, иначе они интерпретируются как int. Вы можете определить свои собственные структуры вручную. Типы переменных, такие как Python dict, не поддерживаются, но, конечно, вы можете использовать взлом с указателями void, если это абсолютно необходимо. Пример двойного аргумента: lib.Foo_baz (self.obj, ctypes.c_double (3.5))
+1 Отлично, спасибо! Возможно, это общеизвестно, но для будущих поколений я использовал osx и для компиляции пришлось заменить -soname на -install_name.
В этом примере в указателях функций должны быть установлены restype и argtypes. restype по умолчанию - c_int, который усекает 64-битный указатель. В restype=c_void_p значение указателя защищено, но по-прежнему преобразуется в целое число Python. В качестве аргумента это снова значение по умолчанию c_int, если только argtypes не указан или он вручную не завернут в c_void_p.
@DustinBoswell: я расширил код этого ответа, включив функции с двойными и целочисленными параметрами и целочисленным возвращаемым значением - код находится на pastebin (pastebin.com/pyUXf3RW) и (pastebin.com/0D700WPb). Однако я не знаю, где разместить функцию function.restype = c_double, чтобы вернуть двойное значение, чтобы не сказать, как передать параметр STL vector <vector <double>>, который является моей конечной потребностью.
Ссылка на ctypes в этом ответе не работает: docs.python.org/lib/module-ctypes.html
Не забудьте потом удалить указатель, например, предоставление функции Foo_delete и вызов ее либо из деструктора Python, либо упаковка объекта в ресурс.
Я получаю OSError: ./libfoo.so: undefined symbol: XGetImage, есть идеи, что может быть не так?
Для компиляции dylib на Mac вам понадобится: g++ -dynamiclib -undefined suppress -flat_namespace *.o -o something.dylib, как: stackoverflow.com/questions/3532589/…
У меня возникла ошибка -soname not found, поэтому я выполнил эту команду. "g ++ -fPIC -shared -o libfoo.so foo.o"
аналогичный пример / руководство с немного большей функциональностью: auctoris.co.uk/2017/04/29/…
В моей версии foo->bar() я получил segfault при доступе к переменной-члену класса (не в приведенном выше примере). В этом случае помогло добавление argtypes и restype. Смотрите здесь stackoverflow.com/questions/13754220/…
Я думаю, что cffi для python может быть вариантом.
The goal is to call C code from Python. You should be able to do so without learning a 3rd language: every alternative requires you to learn their own language (Cython, SWIG) or API (ctypes). So we tried to assume that you know Python and C and minimize the extra bits of API that you need to learn.
http://cffi.readthedocs.org/en/release-0.7/
Я думаю, это может вызвать только c (не C++), но все же +1 (мне очень нравится cffi).
Я начал свое путешествие в привязке 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++ векторы stl передаются как неконстантные ссылки и, следовательно, доступны python в качестве выходных параметров: lobianco.org/antonello/personal:portfolio:portopt
Сначала вы должны решить, какова ваша конкретная цель. Официальная документация Python по расширение и встраивание интерпретатора Python упоминалась выше, я могу добавить хороший обзор бинарных расширений. Сценарии использования можно разделить на 3 категории:
Чтобы дать более широкую перспективу другим заинтересованным сторонам, и поскольку ваш первоначальный вопрос немного расплывчат («к библиотеке C или C++»), я думаю, что эта информация может быть вам интересна. По ссылке выше вы можете прочитать о недостатках использования двоичных расширений и их альтернативах.
Помимо других предложенных ответов, если вам нужен модуль ускорителя, вы можете попробовать Нумба. Он работает «путем генерации оптимизированного машинного кода с использованием инфраструктуры компилятора LLVM во время импорта, выполнения или статически (с использованием прилагаемого инструмента pycc)».
Вопрос в том, как вызвать функцию 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
За подробным руководством вы можете обратиться к моя статья в блоге.
Возможно, стоит отметить, что, хотя ctypes переносимы, ваш код требует специфической для Windows библиотеки C.
Cython определенно подходит, если вы не планируете писать оболочки Java, и в этом случае SWIG может быть предпочтительнее.
Я рекомендую использовать утилиту командной строки runcython, она делает процесс использования Cython чрезвычайно простым. Если вам нужно передать структурированные данные в C++, взгляните на библиотеку Google protobuf, это очень удобно.
Вот минимальный пример, который я сделал, в котором используются оба инструмента:
https://github.com/nicodjimenez/python2cpp
Надеюсь, это может быть полезной отправной точкой.
Существует также pybind11, который похож на облегченную версию Boost.Python и совместим со всеми современными компиляторами C++:
https://pybind11.readthedocs.io/en/latest/
Pytorchpytorch.org/tutorials/advanced/cpp_extension.html Also fully works on VS Community Windows
Пожалуйста, также включите несколько примеров, чтобы сделать его более полезным, чем ответ только по ссылке.
Это действительно выглядит потрясающе! жаль, что я только что нашел это. Я думаю, что для этого стоит переписать код.
Я показал минимальный запускаемый пример по адресу: stackoverflow.com/a/60374990/895245, чтобы продемонстрировать, насколько это круто.
Для современного 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.
На самом деле это не так: Cython - это Python-подобный язык программирования для написания модулей расширения C для Python (код Cython переводится на C вместе с необходимым шаблоном C-API). Он обеспечивает некоторую базовую поддержку C++. Программирование с помощью cppyy включает только Python и C++, без языковых расширений. Он полностью работает и не генерирует автономный код (ленивая генерация масштабируется намного лучше). Он нацелен на современный C++ (включая автоматические экземпляры шаблонов, перемещения, initializer_lists, лямбды и т. д. И т. Д.), А PyPy поддерживается изначально (т. Е. Не через медленный слой эмуляции C-API).
Этот Бумага PyHPC'16 содержит ряд значений тестов. Однако с тех пор на стороне CPython произошли определенные улучшения.
Мне нравится этот подход, потому что вам не нужно выполнять дополнительную работу по интеграции с swig, ctypes или boost.python. Вместо того, чтобы писать код, чтобы заставить python работать с вашим кодом на C++ ... python выполняет тяжелую работу, чтобы понять C++. Предполагая, что это действительно работает.
cppyy очень интересно! Я вижу в документации, что происходит перераспределение и предварительная упаковка. Известно ли, что это хорошо работает с инструментами, которые также упаковывают код Python (например, PyInstaller)? Связано ли это с проектом ROOT или использовать его работу?
Спасибо! Я не знаком с PyInstaller, но «словари», которые упаковывают предварительные объявления, пути и заголовки, представляют собой коды C++, скомпилированные в общие библиотеки. Поскольку cppyy используется для привязки кода C++, я полагаю, что обработка еще некоторого кода C++ должна быть в порядке. И этот код не зависит от Python C-API (только модуль libcppyy), что упрощает работу. Сам cppyy может быть установлен из conda-forge или pypi (pip), так что любая из этих сред наверняка будет работать. Да, cppyy начался как форк PyROOT, но с тех пор он настолько улучшился, что команда ROOT переустанавливает PyROOT поверх cppyy.
Мне нравится 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++.
Минимальный запускаемый пример pybind11
pybind11 ранее упоминался в https://stackoverflow.com/a/38542539/895245, но я хотел бы привести здесь конкретный пример использования и некоторое дальнейшее обсуждение реализации.
В общем, я настоятельно рекомендую pybind11, потому что он действительно прост в использовании: вы просто включаете заголовок, а затем pybind11 использует магию шаблона для проверки класса C++, который вы хотите предоставить Python, и делает это прозрачно.
Обратной стороной этой магии шаблона является то, что он замедляет компиляцию, сразу же добавляя несколько секунд к любому файлу, который использует pybind11, см., Например, расследование, проведенное по этому вопросу. PyTorch соглашается. Предложение по устранению этой проблемы было сделано по адресу: https://github.com/pybind/pybind11/pull/2445
Вот минимальный запускаемый пример, чтобы вы почувствовали, насколько хорош 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 по крупицам со всей содержащейся в нем информацией (члены, методы, дополнительные метаданные ...). Смотрите также:
pybind11 утверждает, что он похож на Boost.Python, который был упомянут в https://stackoverflow.com/a/145436/895245, но более минимален, потому что он свободен от раздувания внутри проекта Boost:
pybind11 is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code. Its goals and syntax are similar to the excellent Boost.Python library by David Abrahams: to minimize boilerplate code in traditional extension modules by inferring type information using compile-time introspection.
The main issue with Boost.Python—and the reason for creating such a similar project—is Boost. Boost is an enormously large and complex suite of utility libraries that works with almost every C++ compiler in existence. This compatibility has its cost: arcane template tricks and workarounds are necessary to support the oldest and buggiest of compiler specimens. Now that C++11-compatible compilers are widely available, this heavy machinery has become an excessively large and unnecessary dependency.
Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~4K lines of code and depend on Python (2.7 or 3.x, or PyPy2.7 >= 5.7) and the C++ standard library. This compact implementation was possible thanks to some of the new C++11 language features (specifically: tuples, lambda functions and variadic templates). Since its creation, this library has grown beyond Boost.Python in many ways, leading to dramatically simpler binding code in many common situations.
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.
Boost.Python - одна из наиболее удобных для пользователя библиотек в Boost, для простого API вызова функций она довольно проста и предоставляет шаблон, который вам придется написать самостоятельно. Это немного сложнее, если вы хотите предоставить объектно-ориентированный API.