Вызов C / C ++ из Python?


521

Какой самый быстрый способ создать привязку Python к библиотеке C или C ++?

(Я использую Windows, если это имеет значение.)

Ответы:


170

Вы должны взглянуть на Boost.Python . Вот краткое введение, взятое с их сайта:

Boost Python Library - это фреймворк для взаимодействия Python и C ++. Он позволяет вам быстро и беспрепятственно представлять функции и объекты классов C ++ для Python и наоборот, не используя специальных инструментов - только ваш компилятор C ++. Он предназначен для незаметного переноса интерфейсов C ++, поэтому вам не нужно вообще менять код C ++, чтобы обернуть его, что делает Boost.Python идеальным для предоставления сторонним библиотекам Python. Использование библиотеки передовых методов метапрограммирования упрощает ее синтаксис для пользователей, так что код переноса выглядит как своего рода декларативный язык определения интерфейса (IDL).


Boost.Python - это одна из наиболее удобных для пользователя библиотек в Boost, для простого API вызова функций она довольно проста и предоставляет шаблон, который вам придется написать самостоятельно. Это немного сложнее, если вы хотите представить объектно-ориентированный API.
jwfearn

15
Boost.Python - худшее, что можно себе представить. Для каждой новой машины и с каждым обновлением это связано с проблемами связывания.
Миллер

15
Спустя почти 11 лет пора задуматься о качестве этого ответа?
Дж Эванс

4
Это все еще лучший подход к интерфейсу Python и C ++?
tushaR

8
Может быть, вы можете попробовать Pybind11, который легче по сравнению с Boost .
jdhao

659

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

14
Это в значительной степени то, что boost.python делает для вас одним вызовом функции.
Мартин Беккет

203
ctypes находится в стандартной библиотеке Python, Swig и Boost нет. Swig и boost полагаются на модули расширения и поэтому привязаны к второстепенным версиям Python, которые не являются независимыми общими объектами. Создание swig или boost-обёрток может быть проблемой, ctypes не предъявляет требований к сборке.
Флориан Беш

25
boost полагается на магию шаблонов voodoo и полностью настраиваемую систему сборки, ctypes полагается на простоту. ctypes - динамический, boost - статический. 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) :-)

Испытав решения, основанные как на 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
Антонелло

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) Я видел ctypes в использовании, и он уродлив и очень подвержен ошибкам с многочисленными подводными камнями 2) Он позволяет вам взять немного кода Python и ускорить его от статической типизации вещей в одиночку 3) написать оболочки Python для методов и объектов C / C ++ несложно 4) он все еще хорошо поддерживается. Это может быть связано с дополнительными рекомендациями по установке на venvs и кросс-компиляции, что заняло немного времени для разработки. Здесь есть очень хорошее 4-часовое видеоурок: youtube.com/watch?v=gMvkiQ-gOW8
Ден-Джейсон,

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 ++ (включая автоматические реализации шаблонов, перемещения, initializer_lists, lambda и т. Д. И т. Д.), А PyPy поддерживается изначально (т.е. не через медленный уровень эмуляции C-API).
Вим Лаврийсен

2
Этот документ 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.
Вим Лаврийсен


15

Я никогда не использовал это, но я слышал хорошие вещи о ctypes . Если вы пытаетесь использовать его с 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 (кстати, переносимый во всех вариантах 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.
Палек


6

Определенно, Cython - это путь, если только вы не планируете писать обертки Java, в этом случае SWIG может быть предпочтительнее.

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

Вот минимальные примеры, которые я сделал, которые используют оба инструмента:

https://github.com/nicodjimenez/python2cpp

Надеюсь, что это может быть полезной отправной точкой.


5

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

  • Модули акселератора : чтобы работать быстрее, чем эквивалентный чистый код Python, запускаемый в CPython.
  • модули-обертки : для представления существующих интерфейсов C в коде Python.
  • низкоуровневый доступ к системе к : для доступа к низкоуровневым функциям среды выполнения CPython, операционной системы или базового оборудования.

Чтобы дать более широкую перспективу для других заинтересованных лиц, и поскольку ваш первоначальный вопрос немного расплывчатый («к библиотеке C или C ++»), я думаю, что эта информация может быть вам интересна. По ссылке выше вы можете прочитать о недостатках использования бинарных расширений и их альтернатив.

Помимо других предложенных ответов, если вы хотите ускоритель, вы можете попробовать Numba . Он работает «путем генерации оптимизированного машинного кода с использованием инфраструктуры компилятора LLVM во время импорта, во время выполнения или статически (с помощью включенного инструмента pycc)».


3

Мне нравится 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 ++.


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 собственного модуля.

Возможно, осознание того, насколько это круто, только впитывается, если вы попытаетесь сделать то же самое вручную с помощью собственного 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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.