Виклик C / C ++ з Python?


521

Який би найшвидший спосіб побудувати прив'язку Python до бібліотеки C або C ++?

(Я використовую Windows, якщо це має значення.)

Відповіді:


170

Ви повинні подивитися на Boost.Python . Ось короткий вступ із їх веб-сайту:

Бібліотека Boost Python є основою для взаємодії Python та C ++. Це дозволяє швидко та безпроблемно піддавати функції C ++ класів та об'єктів Python, і навпаки, не використовуючи спеціальних інструментів - лише ваш компілятор C ++. Він призначений для загортання інтерфейсів C ++ не нав'язливо, так що вам не доведеться взагалі змінювати код C ++ для того, щоб обернути його, що робить Boost.Python ідеальним для викриття сторонніх бібліотек Python. Використання передових методів метапрограмування в бібліотеці спрощує її синтаксис для користувачів, так що код для обгортання набуває виду декларативної мови визначення інтерфейсу (IDL).


Boost.Python - одна з найбільш зручних у користуванні бібліотек Boost, для API простого виклику функцій він є досить простим і забезпечує програму, яку потрібно писати самостійно. Це трохи складніше, якщо ви хочете розкрити об’єктно-орієнтований API.
jwfearn

15
Boost.Python - це найгірше, що можна уявити. Для кожної нової машини та з кожним оновленням це пов'язано з проблемами зв’язку.
мельник

14
Майже через 11 років настає роздум про якість цієї відповіді?
Дж. Еванс

4
Це все-таки найкращий підхід до інтерфейсу python та c ++?
tushaR

8
Можливо, ви можете спробувати pybind11, який легкий у порівнянні з прискореним.
jdhao

659

Модуль 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

14
Це майже те, що boost.python робить для вас в одному дзвінку функції.
Мартін Бекетт

203
ctypes є в стандартній бібліотеці python, swig та boost не є. Swig і boost покладаються на модулі розширення, і тому вони прив'язані до другорядних версій python, які не мають незалежних спільних об'єктів. побудова плівок або збільшити обгортки може бути болем, типи не вимагають складання.
Флоріан Бьош

25
boost покладається на магію шаблону вуду та цілком власну систему побудови, ctypes покладається на простоту. ctypes динамічний, прискорення статичне. ctypes можуть обробляти різні версії бібліотек. прискорення не може.
Флоріан Бьош

32
У Windows мені довелося вказати __declspec (dllexport) у своїх підписах функції для Python, щоб мати можливість їх бачити. З вищенаведеного прикладу це відповідатиме: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Алан Макдональд

13
Не забудьте потім видалити покажчик, наприклад, надавши Foo_deleteфункцію та викликаючи її з деструктора python або загорнувши об’єкт у ресурс .
Adversus

58

Найшвидший спосіб зробити це за допомогою 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 може також використовуватися для упаковки структур і класів у проксі-класи на цільовій мові - викриваючи основні функціональні можливості дуже природним чином.


50

Я розпочав свою подорож у прив'язці 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

Реальна реалізація випадку, коли в коді C ++ stl-вектори передаються як посилання const, а тому пітхон
Antonello

33

Є також така pybind11, що схожа на легку версію Boost.Python та сумісна з усіма сучасними компіляторами C ++:

https://pybind11.readthedocs.io/en/latest/


1
Сьогодні !! 2020 рік. Це має бути головна відповідь! Це лише бібліотека заголовка шаблону. Багато величезних відповідних проектів рекомендує його, як-от Pytorch pytorch.org/tutorials/advanced/cpp_extension.html Також повністю працює у VS CommunityWindows
eusoubrasileiro

30

Ознайомтеся з pyrex або Cython . Вони є Python-подібними мовами для взаємодії між C / C ++ та Python.


1
+1 для Cython! Я не пробував cffi, тому не можу сказати, що краще, але у мене був дуже хороший досвід роботи з Cython - ви все ще пишете код Python, але ви можете використовувати C у ньому. Мені було дещо важко налаштувати процес збирання з Cython, про що я пізніше пояснив у публікації в блозі: martinsosic.com/development/2016/02/08/…
Martinsos

Можливо, ви хочете вдосконалити відповідь, щоб вона більше не була лише посиланням.
Аделін

Я використовую Cython близько тижня, і мені це дуже подобається: 1) Я бачив, що вживані типи моделей, це UGLY і дуже схильне до помилок з численними підводними каменями 2) Це дозволяє взяти якийсь код Python і прискорити його від того, щоб статично вводити речі 3) Просте писати обгортки Python для методів та об'єктів C / C ++ просто) 4) Це все ще добре підтримується. Це могло б зробити більше вказівок щодо встановлення на venvs та крос-компіляції, що потребувало небагато часу для відпрацювання. Тут дуже хороший 4-годинний відеоурок: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason

22

Для сучасного 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.


3
Насправді це не так: Cython - це мова програмування, схожа на Python, для написання модулів розширення C для Python (код Cython переводиться на C разом з необхідною коробкою C-API). Він забезпечує деяку базову підтримку C ++. Програмування за допомогою cppyy включає лише Python та C ++, без розширень мови. Він повністю працює і не генерує офлайн-код (ліниве покоління набагато краще масштабує). Він орієнтований на сучасний C ++ (включаючи автоматичні екземпляри шаблонів, переміщення, ініціалізатори_лістів, лямбда, тощо, тощо), а PyPy підтримується на самому собі (тобто не через повільний емуляційний шар C-API).
Вім Лаврісенсен

2
Цей документ PyHPC'16 містить цілий ряд контрольних номерів. З цього часу на стороні CPython відбулися певні вдосконалення.
Вім Лаврісенсен

Мені подобається цей підхід , тому що ви не повинні робити додаткову роботу інтеграції з swig, ctypesабо boost.python. Замість того, щоб вам писати код, щоб змусити python працювати з вашим кодом c ++ ... python робить важку роботу, щоб з'ясувати c ++. Якщо припустити, що це справді працює.
Тревор Бойд Сміт

cppyy дуже цікаво! Я бачу в документах, що здійснюється перерозподіл та попередня упаковка. Чи відомо, що це добре працює з інструментами, які пакують код python (наприклад, PyInstaller)? І чи пов’язано це з проектом ROOT, або він підтримує його роботу?
JimB

Дякую! Я не знайомий з PyInstaller, але "словники", які пакують декларації, шляхи та заголовки вперед, - це C ++-коди, зібрані в спільні конверти. Оскільки cppyy використовується для прив’язки коду C ++, я припускаю, що обробка ще деякого коду C ++ має бути добре. І цей код не залежить від Python C-API (лише модуль libcppyy), що спрощує речі. Сам cppyy може бути встановлений з conda-forge або pypi (pip), тому будь-яке з цих середовищ працює точно. Так, cppyy почався як вилка від PyROOT, але з тих пір настільки покращився, що команда ROOT випускає PyROOT поверх cppyy.
Вім Лаврісенсен


15

Я ніколи його не використовував, але чув хороші речі про типи . Якщо ви намагаєтеся використовувати його з C ++, не забудьте уникнути керування іменами через extern "C". Дякую за коментар, Флоріан Бьош.


13

Я думаю, що cffi для python може бути варіантом.

Мета - викликати код C з Python. Ви повинні це зробити, не вивчаючи 3-ї мови: кожна альтернатива вимагає, щоб ви вивчали власну мову (Cython, SWIG) або API (ctypes). Тому ми спробували припустити, що ви знаєте Python та C та мінімізувати зайві біти API, які вам потрібно вивчити.

http://cffi.readthedocs.org/en/release-0.7/


2
Я думаю, що це може викликати лише c (не c ++), все ще +1 (мені дуже подобається cffi).
Енді Хайден

8

Питання в тому, як викликати функцію 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

Для детального посібника ви можете звернутися до моєї статті блогу .


Можливо, варто відзначити, що, хоча ctypes є портативними, для вашого коду потрібна спеціалізована для Windows бібліотека.
Palec


6

Cython - це, безумовно, шлях, якщо ви не плануєте писати обгортки Java, і в цьому випадку SWIG може бути кращим.

Я рекомендую використовувати runcythonутиліту командного рядка, це робить процес використання Cython надзвичайно простим. Якщо вам потрібно передати структуровані дані на C ++, подивіться на бібліотеку протобуфа Google, це дуже зручно.

Ось мінімальний я приклад, який використовую обидва інструменти:

https://github.com/nicodjimenez/python2cpp

Сподіваюся, це може бути корисною відправною точкою.


5

Спочатку слід визначитися, яка ваша конкретна мета. Офіційна документація Python щодо розширення та вбудовування інтерпретатора Python була згадана вище, я можу додати хороший огляд бінарних розширень . Випадки використання можна розділити на 3 категорії:

  • модулі акселератора : працювати швидше, ніж еквівалентний чистий код Python, що працює в CPython.
  • модулі обгортки : піддавати існуючі інтерфейси C коду Python.
  • низький рівень доступу до системи : для доступу до функцій нижчого рівня під час виконання CPython, операційної системи або базового обладнання.

Для того, щоб дати деяку більш широку перспективу для інших зацікавлених людей, і оскільки ваше початкове запитання трохи розпливчасте ("до бібліотеки С або С ++"), я думаю, ця інформація може бути цікавою для вас. На посиланні вище можна прочитати про недоліки використання бінарних розширень та його альтернатив.

Крім інших запропонованих відповідей, якщо ви хочете модуль прискорювача, ви можете спробувати Numba . Він працює "шляхом генерації оптимізованого машинного коду за допомогою інфраструктури компілятора LLVM під час імпорту, часу виконання або статично (використовуючи доданий інструмент pycc)".


3

Я люблю 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 ++.


2

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.