Відповіді:
Ви повинні подивитися на Boost.Python . Ось короткий вступ із їх веб-сайту:
Бібліотека Boost Python є основою для взаємодії Python та C ++. Це дозволяє швидко та безпроблемно піддавати функції C ++ класів та об'єктів Python, і навпаки, не використовуючи спеціальних інструментів - лише ваш компілятор C ++. Він призначений для загортання інтерфейсів C ++ не нав'язливо, так що вам не доведеться взагалі змінювати код C ++ для того, щоб обернути його, що робить Boost.Python ідеальним для викриття сторонніх бібліотек Python. Використання передових методів метапрограмування в бібліотеці спрощує її синтаксис для користувачів, так що код для обгортання набуває виду декларативної мови визначення інтерфейсу (IDL).
Модуль ctypes є частиною стандартної бібліотеки, а тому є більш стабільним і широкодоступним, ніж swig , який завжди викликав у мене проблеми .
За допомогою ctypes вам потрібно задовольнити будь-яку залежність часу компіляції від python, і ваше зв'язування буде працювати на будь-якому python, що має ctypes, а не тільки на той, з якого було складено.
Припустимо, у вас є простий клас прикладу C ++, з яким ви хочете поговорити у файлі під назвою foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Оскільки типи можуть спілкуватися лише з функціями 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) :-)
Спробувавши рішення, що базуються як на типах, так і на 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
Ознайомтеся з pyrex або Cython . Вони є Python-подібними мовами для взаємодії між C / C ++ та Python.
Для сучасного 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.
Я ніколи його не використовував, але чув хороші речі про типи . Якщо ви намагаєтеся використовувати його з C ++, не забудьте уникнути керування іменами через extern "C"
. Дякую за коментар, Флоріан Бьош.
Я думаю, що cffi для python може бути варіантом.
Мета - викликати код C з Python. Ви повинні це зробити, не вивчаючи 3-ї мови: кожна альтернатива вимагає, щоб ви вивчали власну мову (Cython, SWIG) або API (ctypes). Тому ми спробували припустити, що ви знаєте Python та C та мінімізувати зайві біти API, які вам потрібно вивчити.
Питання в тому, як викликати функцію C з Python, якщо я правильно зрозумів. Тоді найкраща ставка - це Ctypes (BTW портативний у всіх варіантах 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 ++, подивіться на бібліотеку протобуфа Google, це дуже зручно.
Ось мінімальний я приклад, який використовую обидва інструменти:
https://github.com/nicodjimenez/python2cpp
Сподіваюся, це може бути корисною відправною точкою.
Спочатку слід визначитися, яка ваша конкретна мета. Офіційна документація Python щодо розширення та вбудовування інтерпретатора Python була згадана вище, я можу додати хороший огляд бінарних розширень . Випадки використання можна розділити на 3 категорії:
Для того, щоб дати деяку більш широку перспективу для інших зацікавлених людей, і оскільки ваше початкове запитання трохи розпливчасте ("до бібліотеки С або С ++"), я думаю, ця інформація може бути цікавою для вас. На посиланні вище можна прочитати про недоліки використання бінарних розширень та його альтернатив.
Крім інших запропонованих відповідей, якщо ви хочете модуль прискорювача, ви можете спробувати Numba . Він працює "шляхом генерації оптимізованого машинного коду за допомогою інфраструктури компілятора LLVM під час імпорту, часу виконання або статично (використовуючи доданий інструмент pycc)".
Я люблю 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 погоджується .
Ось мінімальний приклад, який можна виконати, щоб ви могли відчути, як приголомшливий 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 - це легка бібліотека, призначена лише для заголовків, яка розкриває типи 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.