Привязки к Python: Вызов C или C++ из Python
Оглавление
Вы разработчик на Python, у вас есть библиотека на C или C++, которую вы хотели бы использовать на Python? Если это так, то Привязки к Python позволяют вызывать функции и передавать данные из Python в C или C++, позволяющий вам воспользоваться преимуществами обоих языков. В этом руководстве вы увидите обзор некоторых инструментов, которые можно использовать для создания привязок Python.
В этом руководстве вы узнаете о:
- Почему вы хотите вызывать C или C++ из Python
- Как передавать данные между C и Python
- Какие инструменты и методы могут помочь вам создать привязки Python
Это руководство предназначено для разработчиков Python среднего уровня. Оно предполагает базовые знания Python и некоторое понимание функций и типов данных в C или C++. Вы можете ознакомиться со всеми примерами кода, которые вы увидите в этом руководстве, перейдя по ссылке ниже:
Получите пример кода: Нажмите здесь, чтобы получить пример кода, который вы будете использовать для изучения привязок Python в этом руководстве.
Давайте углубимся в изучение привязок Python!
Обзор привязок Python
Прежде чем вы углубитесь в как вызывать C из Python, полезно потратить некоторое время на почему . Есть несколько ситуаций, когда создание привязок Python для вызова библиотеки C является отличной идеей:
-
У вас уже есть большая, протестированная, стабильная библиотека, написанная на C++, которой вы хотели бы воспользоваться в Python. Это может быть коммуникационная библиотека или библиотека для взаимодействия с определенным оборудованием. Что она делает, не имеет значения.
-
Вы хотите ускорить выполнение определенного раздела вашего кода на Python, преобразовав критический раздел в C. C не только обеспечивает более высокую скорость выполнения, но и позволяет вам освободиться от ограничений GIL, при условии, что вы будете осторожны.
-
Вы хотите использовать инструменты тестирования на Python для проведения крупномасштабного тестирования своих систем.
Все вышеперечисленное - отличная причина научиться создавать привязки Python для взаимодействия с вашей библиотекой C.
Примечание: На протяжении всего этого руководства вы будете создавать привязки Python как к C , так и к C++. Большинство общих понятий применимы к обоим языкам, и поэтому C будет использоваться, если только между этими двумя языками нет особой разницы. В общем, каждый из инструментов будет поддерживать либо C , либо C++, но не оба сразу.
Давайте начнем!
Сортировка типов данных
Подождите! Прежде чем вы начнете писать привязки на Python, взгляните на то, как Python и C хранят данные и какие проблемы это может вызвать. Сначала давайте определим сортировку. Это понятие определяется Википедией следующим образом:
Процесс преобразования представления объекта в памяти в формат данных, пригодный для хранения или передачи. (Источник)
Для ваших целей маршалинг - это то, что делают привязки Python, когда они подготавливают данные для их переноса с Python на C или наоборот. Привязки Python должны выполнять маршалинг, потому что Python и C хранят данные по-разному. C хранит данные в памяти в максимально сжатом виде. Если вы используете uint8_t, то для этого потребуется всего 8 бит памяти.
В Python, с другой стороны, все является объектом . Это означает, что каждое целое число использует несколько байт в памяти. Сколько их будет зависеть от того, какую версию Python вы используете, вашей операционной системы и других факторов. Это означает, что ваши привязки Python должны будут преобразовать Целое число C в Целое число Python для каждого целого числа, передаваемого через границу.
Другие типы данных имеют схожие связи между двумя языками. Давайте рассмотрим каждый из них по очереди:
-
Целые числа хранят счетные числа. Python хранит целые числа с произвольной точностью, что означает, что вы можете хранить очень-очень большие числа. C определяет точные размеры целых чисел. Вам нужно знать о размерах данных при переходе с одного языка на другой, чтобы предотвратить переполнение целочисленных переменных Python целочисленными значениями C.
-
Числа с плавающей запятой - это числа с десятичным разрядом. Python может хранить гораздо большие (и гораздо меньшие) числа с плавающей запятой, чем C. Это означает, что вам также придется обращать внимание на эти значения, чтобы убедиться, что они остаются в пределах допустимого диапазона.
-
Комплексные числа это числа с мнимой частью. В то время как в Python есть встроенные комплексные числа, а в C есть комплексные числа, нет встроенного метода для сортировки между ними. Чтобы упорядочить комплексные числа, вам нужно создать
structилиclassв коде C для управления ими. -
Строки представляют собой последовательности символов. Поскольку strings - это такой распространенный тип данных, с ними будет довольно сложно работать при создании привязок на Python. Как и в случае с другими типами данных, Python и C хранят строки в совершенно разных форматах. (В отличие от других типов данных, в этой области C и C++ также отличаются друг от друга, что добавляет веселья!) Каждое из рассмотренных вами решений имеет несколько разные методы работы со строками.
-
Логические переменные могут иметь только два значения. Поскольку они поддерживаются на C, их сортировка окажется довольно простой.
Помимо преобразования типов данных, есть и другие проблемы, о которых вам нужно подумать при создании привязок Python. Давайте продолжим их изучение.
Понимание изменяемых и неизменяемых значений
В дополнение ко всем этим типам данных, вы также должны знать, как объекты Python могут быть изменяемыми или неизменяемыми. C использует аналогичную концепцию с параметрами функций, когда речь идет о передаче по значению или передаче по ссылке. В C все параметры передаются по значению. Если вы хотите разрешить функции изменять переменную в вызывающем объекте, то вам нужно передать указатель на эту переменную.
Возможно, вам интересно, можно ли обойти ограничение на неизменяемость, просто передав неизменяемый объект в C с помощью указателя. Если вы не перейдете к уродливым и непереносимым крайностям, Python не выдаст вам указатель на объект, так что это просто не сработает. Если вы хотите изменить объект Python на C, вам нужно будет предпринять дополнительные шаги для достижения этой цели. Эти шаги будут зависеть от того, какие инструменты вы используете, как вы увидите ниже.
Итак, вы можете добавить неизменяемость в свой контрольный список элементов, которые следует учитывать при создании привязок Python. Ваша последняя остановка на пути к созданию этого контрольного списка - это то, как по-разному Python и C справляются с управлением памятью.
Управление памятью
C и Python управляют памятью по-разному. В C разработчик должен управлять всеми выделениями памяти и следить за тем, чтобы они освобождались один и только один раз. Python позаботится об этом за вас, используя сборщик мусора.
Хотя каждый из этих подходов имеет свои преимущества, он создает дополнительные трудности при создании привязок на Python. Вам нужно будет знать , где была выделена память для каждого объекта, и убедиться, что она освобождается только по ту сторону языкового барьера.
Например, объект Python создается, когда вы устанавливаете x = 3. Память для этого выделяется на стороне Python, и ее необходимо обработать. К счастью, с объектами Python довольно сложно делать что-либо еще. Взгляните на обратное в C, где вы непосредственно выделяете блок памяти:
int* iPtr = (int*)malloc(sizeof(int));
Когда вы это сделаете, вам нужно убедиться, что этот указатель освобожден в C. Для этого может потребоваться вручную добавить код в ваши привязки Python.
Это завершает ваш список общих тем. Давайте начнем настраивать вашу систему, чтобы вы могли написать какой-нибудь код!
Настройка среды
В этом руководстве вы будете использовать уже существующие библиотеки C и C++ из реального репозитория Python на GitHub, чтобы продемонстрировать тестирование каждого инструмента. Цель состоит в том, чтобы вы могли использовать эти идеи для любой библиотеки C. Чтобы следовать всем приведенным здесь примерам, вам потребуется следующее:
- Установлена библиотека C++ и известен путь для вызова командной строки
- Python инструменты разработки:
- Для Linux это пакет
python3-devилиpython3-devel, в зависимости от вашего дистрибутива. - Для Windows существует несколько вариантов.
- Для Linux это пакет
- Python 3.6 или более поздней версии
- A виртуальная среда (рекомендуется, но не требуется)
- Инструмент
invoke
Последний вариант может показаться вам новым, поэтому давайте рассмотрим его поближе.
С помощью инструмента invoke
invoke это инструмент, который вы будете использовать для создания и тестирования ваших привязок к Python в этом руководстве. Он имеет ту же цель, что и make, но использует Python вместо Makefiles. Вам нужно будет установить invoke в вашей виртуальной среде с помощью pip:
$ python3 -m pip install invoke
Чтобы запустить его, введите invoke, а затем задачу, которую вы хотите выполнить:
$ invoke build-cmult
==================================================
= Building C Library
* Complete
Чтобы узнать, какие задачи доступны, воспользуйтесь опцией --list:
$ invoke --list
Available tasks:
all Build and run all tests
build-cffi Build the CFFI Python bindings
build-cmult Build the shared library for the sample C code
build-cppmult Build the shared library for the sample C++ code
build-cython Build the cython extension module
build-pybind11 Build the pybind11 wrapper library
clean Remove any built objects
test-cffi Run the script to test CFFI
test-ctypes Run the script to test ctypes
test-cython Run the script to test Cython
test-pybind11 Run the script to test PyBind11
Обратите внимание, что когда вы заглянете в файл tasks.py, в котором определены задачи invoke, вы увидите, что вторая задача в списке называется build_cffi. Однако в выходных данных --list это отображается как build-cffi. Знак минус (-) не может использоваться как часть имени Python, поэтому вместо него в файле используется символ подчеркивания (_).
Для каждого из инструментов, которые вы будете изучать, будут определены build- и test- задачи. Например, чтобы запустить код для CFFI, вы могли бы ввести invoke build-cffi test-cffi. Исключением является ctypes, поскольку для ctypes нет этапа сборки. Кроме того, для удобства добавлены две специальные задачи:
invoke allзапускает задачи сборки и тестирования для всех инструментов.invoke cleanудаляет все сгенерированные файлы.
Теперь, когда у вас есть представление о том, как запускать код, давайте взглянем на код на C, который вы будете создавать, прежде чем перейти к обзору инструментов.
Исходный код на C или C++
В каждом из приведенных ниже примеров вы будете создавать привязки Python для одной и той же функции на C или C++. Эти разделы предназначены для того, чтобы дать вам представление о том, как выглядит каждый метод, а не для подробного ознакомления с этим инструментом, поэтому функция, которую вы будете использовать, невелика. Функция, для которой вы создадите привязки Python, принимает int и float в качестве входных параметров и возвращает float, которое является произведением двух чисел:
// cmult.c
float cmult(int int_param, float float_param) {
float return_value = int_param * float_param;
printf(" In cmult : int: %d float %.1f returning %.1f\n", int_param,
float_param, return_value);
return return_value;
}
Функции C и C++ практически идентичны, за исключением незначительных различий в названии и строке. Вы можете получить копию всего кода, перейдя по ссылке ниже:
Получите пример кода: Нажмите здесь, чтобы получить пример кода, который вы будете использовать для изучения привязок Python в этом руководстве.
Теперь у вас есть клонированный репозиторий и установленные инструменты, вы можете создавать и тестировать инструменты. Итак, давайте рассмотрим каждый раздел ниже!
ctypes
Вы начнете с ctypes,, который является инструментом в стандартной библиотеке для создания привязок Python. Он предоставляет низкоуровневый набор инструментов для загрузки разделяемых библиотек и сортировки данных между Python и C.
Как это устанавливается
Одним из больших преимуществ ctypes является то, что он является частью стандартной библиотеки Python. Он был добавлен в Python версии 2.5, так что вполне вероятно, что он у вас уже есть. Вы можете import сделать это точно так же, как вы делаете с sys или time модулями.
Вызов функции
Весь код для загрузки вашей библиотеки C и вызова функции будет содержаться в вашей программе на Python. Это замечательно, поскольку в вашем процессе нет никаких дополнительных шагов. Вы просто запускаете свою программу, и обо всем позаботятся. Чтобы создать свои привязки к Python в ctypes, вам нужно выполнить следующие действия:
- Загрузите свою библиотеку.
- Перенесите некоторые из ваших входных параметров.
- Укажите
ctypesтип возвращаемого значения вашей функции.
Вы рассмотрите каждый из них по очереди.
Загрузка библиотеки
ctypes предоставляет несколько способов загрузки общей библиотеки, некоторые из которых зависят от платформы. В вашем примере вы создадите объект ctypes.CDLL напрямую, указав полный путь к нужной вам общей библиотеке:
# ctypes_test.py
import ctypes
import pathlib
if __name__ == "__main__":
# Load the shared library into ctypes
libname = pathlib.Path().absolute() / "libcmult.so"
c_lib = ctypes.CDLL(libname)
Это будет работать в тех случаях, когда общая библиотека находится в том же каталоге, что и ваш скрипт на Python, но будьте осторожны при попытке загрузить библиотеки из пакетов, отличных от ваших привязок на Python. В документации ctypes содержится множество подробных сведений о загрузке библиотек и поиске путей, которые зависят от платформы и конкретной ситуации.
ПРИМЕЧАНИЕ: Во время загрузки библиотеки могут возникнуть многие проблемы, связанные с платформой. Лучше всего вносить постепенные изменения, как только вы получите работающий пример.
Теперь, когда библиотека загружена в Python, вы можете попробовать вызвать ее!
Вызов вашей функции
Помните, что прототип функции для вашей функции C выглядит следующим образом:
// cmult.h
float cmult(int int_param, float float_param);
Вам нужно передать целое число и число с плавающей точкой, и вы можете ожидать, что будет возвращено значение с плавающей точкой. Целые числа и числа с плавающей точкой имеют встроенную поддержку как в Python, так и в C, поэтому вы ожидаете, что этот случай будет работать для разумных значений.
Как только вы загрузите библиотеку в свои привязки Python, функция станет атрибутом c_lib, который является объектом CDLL, созданным вами ранее. Вы можете попробовать вызвать это просто так:
x, y = 6, 2.3
answer = c_lib.cmult(x, y)
К сожалению! Это не работает. В примере репозитория эта строка закомментирована, поскольку в ней произошел сбой. Если вы попытаетесь выполнить этот вызов, Python выдаст сообщение об ошибке:
$ invoke test-ctypes
Traceback (most recent call last):
File "ctypes_test.py", line 16, in <module>
answer = c_lib.cmult(x, y)
ctypes.ArgumentError: argument 2: <class 'TypeError'>: Don't know how to convert parameter 2
Похоже, вам нужно сообщить ctypes о любых параметрах, которые не являются целыми числами. ctypes ничего не знает о функции, если вы не укажете это явно. Предполагается, что любой параметр, который не помечен иначе, является целым числом. ctypes не знает, как преобразовать значение 2.3, сохраненное в y, в целое число, поэтому он не работает.
Чтобы исправить это, вам нужно создать c_float из числа. Вы можете сделать это в строке, в которой вызываете функцию:
# ctypes_test.py
answer = c_lib.cmult(x, ctypes.c_float(y))
print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
Теперь, когда вы запускаете этот код, он возвращает произведение двух чисел, которые вы ввели:
$ invoke test-ctypes
In cmult : int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 48.0
Подождите минутку... 6 умноженное на 2.3 не является 48.0!
Оказывается, что, как и во входных параметрах, ctypes предполагается, что ваша функция возвращает int. На самом деле, ваша функция возвращает float, который неправильно сортируется. Как и в случае с входным параметром, вам нужно указать ctypes, чтобы использовать другой тип. Синтаксис здесь немного отличается:
# ctypes_test.py
c_lib.cmult.restype = ctypes.c_float
answer = c_lib.cmult(x, ctypes.c_float(y))
print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
Этого должно хватить. Давайте запустим всю цель test-ctypes и посмотрим, что у вас получится. Помните, что первая часть вывода - это , прежде чем вы исправили restype функции на значение с плавающей точкой:
$ invoke test-ctypes
==================================================
= Building C Library
* Complete
==================================================
= Testing ctypes Module
In cmult : int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 48.0
In cmult : int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 13.8
Так-то лучше! Хотя первая, неисправленная версия возвращает неверное значение, ваша исправленная версия соответствует функции C. И C, и Python получают одинаковый результат! Теперь, когда это работает, взгляните на то, почему вы можете использовать, а можете и не использовать ctypes.
Сильные и слабые стороны
Самым большим преимуществом ctypes перед другими инструментами, которые вы здесь рассмотрите, является то, что он встроен в стандартную библиотеку. Это также не требует дополнительных действий, поскольку вся работа выполняется как часть вашей программы на Python.
Кроме того, используемые концепции являются низкоуровневыми, что делает упражнения, подобные тому, которое вы только что выполнили, выполнимыми. Однако более сложные задачи становятся громоздкими из-за отсутствия автоматизации. В следующем разделе вы увидите инструмент, который немного автоматизирует этот процесс.
CFFI
CFFI это Интерфейс внешней функции C для Python. Для создания привязок на Python используется более автоматизированный подход. CFFI есть несколько способов создания и использования привязок на Python. Есть два варианта на выбор, что дает вам четыре возможных режима:
-
ABI против API: Режим API использует компилятор C для создания полноценного модуля Python, в то время как режим ABI загружает общую библиотеку и взаимодействует с ней напрямую. Без запуска компилятора корректное определение структур и параметров может привести к ошибкам. В документации настоятельно рекомендуется использовать режим API.
-
встроенный и автономный: Разница между этими двумя режимами заключается в выборе между скоростью и удобством:
- Встроенный режим компилирует привязки Python при каждом запуске вашего скрипта. Это удобно, так как вам не нужен дополнительный шаг сборки. Однако это замедляет работу вашей программы.
- В автономном режиме требуется выполнить дополнительный шаг для создания привязок Python за один раз, а затем использовать их при каждом запуске программы. Это намного быстрее, но для вашего приложения это может не иметь значения.
В этом примере вы будете использовать автономный режим API, который позволяет создавать самый быстрый код и, в целом, выглядит аналогично другим привязкам Python, которые вы создадите позже в этом руководстве.
Как это устанавливается
Поскольку CFFI не входит в стандартную библиотеку, вам необходимо установить ее на свой компьютер. Рекомендуется создать для этого виртуальную среду. К счастью, CFFI устанавливается с pip:
$ python3 -m pip install cffi
Это приведет к установке пакета в вашу виртуальную среду. Если вы уже установили его из requirements.txt, то об этом следует позаботиться. Вы можете ознакомиться с requirements.txt, перейдя в репозиторий по ссылке ниже:
Получите пример кода: Нажмите здесь, чтобы получить пример кода, который вы будете использовать для изучения привязок Python в этом руководстве.
Теперь, когда вы установили CFFI, пришло время попробовать!
Вызов функции
В отличие от ctypes, с помощью CFFI вы создаете полноценный модуль Python. Вы сможете использовать import модуль, как и любой другой модуль в стандартной библиотеке. Для создания вашего модуля на Python вам потребуется проделать некоторую дополнительную работу. Чтобы использовать ваши CFFI привязки к Python, вам нужно выполнить следующие шаги:
- Напишите некоторый код на Python, описывающий привязки.
- Запустите этот код, чтобы сгенерировать загружаемый модуль.
- Измените вызывающий код, чтобы импортировать и использовать только что созданный модуль.
Это может показаться сложной работой, но вы пройдете через каждый из этих шагов и увидите, как это работает.
Запишите привязки
CFFI предоставляет методы для чтения заголовочного файла C, чтобы выполнить большую часть работы при создании привязок Python. В документации к CFFI код для этого помещен в отдельный файл на Python. В этом примере вы разместите этот код непосредственно в инструменте сборки invoke, который использует файлы на Python в качестве входных данных. Чтобы использовать CFFI, вы начинаете с создания объекта cffi.FFI, который предоставляет три необходимых метода:
# tasks.py
import cffi
...
""" Build the CFFI Python bindings """
print_banner("Building CFFI Module")
ffi = cffi.FFI()
Как только у вас будет FFI, вы будете использовать .cdef() для автоматической обработки содержимого заголовочного файла. Это создаст функции-оболочки для упорядочивания данных из Python:
# tasks.py
this_dir = pathlib.Path().absolute()
h_file_name = this_dir / "cmult.h"
with open(h_file_name) as h_file:
ffi.cdef(h_file.read())
Чтение и обработка заголовочного файла - это первый шаг. После этого вам нужно использовать .set_source() для описания исходного файла, который CFFI сгенерирует:
# tasks.py
ffi.set_source(
"cffi_example",
# Since you're calling a fully-built library directly, no custom source
# is necessary. You need to include the .h files, though, because behind
# the scenes cffi generates a .c file that contains a Python-friendly
# wrapper around each of the functions.
'#include "cmult.h"',
# The important thing is to include the pre-built lib in the list of
# libraries you're linking against:
libraries=["cmult"],
library_dirs=[this_dir.as_posix()],
extra_link_args=["-Wl,-rpath,."],
)
Вот разбивка параметров, которые вы вводите:
-
"cffi_example"это базовое имя для исходного файла, который будет создан в вашей файловой системе.CFFIсгенерирует.cфайл, скомпилируйте его в файл.oи свяжите с файлом.<system-description>.soили.<system-description>.dll. -
'#include "cmult.h"'это пользовательский исходный код на языке Си, который будет включен в сгенерированный исходный код перед его компиляцией. Здесь вы просто добавляете файл.h, для которого вы создаете привязки, но его можно использовать для некоторых интересных настроек. -
libraries=["cmult"]сообщает компоновщику имя вашей ранее существовавшей библиотеки C. Это список, поэтому при необходимости вы можете указать несколько библиотек. -
library_dirs=[this_dir.as_posix(),]это список каталогов, который сообщает компоновщику, где искать приведенный выше список библиотек. -
extra_link_args=['-Wl,-rpath,.']это набор опций, которые генерируют общий объект, который будет искать по текущему пути (.) другие библиотеки, которые ему нужны для загрузки.
Создайте привязки Python
Вызов .set_source() не создает привязок Python. Он только устанавливает метаданные для описания того, что будет сгенерировано. Чтобы создать привязки Python, вам нужно вызвать .compile():
# tasks.py
ffi.compile()
Это завершает работу, генерируя файл .c, файл .o и общую библиотеку. Задача invoke, с которой вы только что ознакомились, может быть выполнена в командной строке для создания привязок Python:
$ invoke build-cffi
==================================================
= Building C Library
* Complete
==================================================
= Building CFFI Module
* Complete
У вас есть свои CFFI привязки к Python, так что пришло время запустить этот код!
Вызов вашей функции
После всей проделанной вами работы по настройке и запуску компилятора CFFI использование сгенерированных привязок Python выглядит так же, как и при использовании любого другого модуля Python:
# cffi_test.py
import cffi_example
if __name__ == "__main__":
# Sample data for your call
x, y = 6, 2.3
answer = cffi_example.lib.cmult(x, y)
print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
Вы импортируете новый модуль, а затем можете напрямую вызвать cmult(). Чтобы протестировать его, воспользуйтесь задачей test-cffi:
$ invoke test-cffi
==================================================
= Testing CFFI Module
In cmult : int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 13.8
При этом запускается ваша программа cffi_test.py, которая тестирует новые привязки Python, созданные вами с помощью CFFI. На этом завершается раздел о написании и использовании ваших привязок CFFI Python.
Сильные и слабые стороны
Может показаться, что ctypes требует меньше усилий, чем пример CFFI, который вы только что видели. Хотя это верно для данного варианта использования, CFFI масштабируется для более крупных проектов намного лучше, чем ctypes, благодаря автоматизации большей части переноса функций.
CFFI также обеспечивает совершенно иной пользовательский опыт. ctypes позволяет загружать уже существующую библиотеку C непосредственно в вашу программу на Python. CFFI, с другой стороны, создает новый модуль на Python, который можно загружать, как и другие модули на Python.
Более того, с помощью метода out-of-line-API, который вы использовали выше, ограничение по времени на создание привязок Python выполняется один раз при его создании и не происходит каждый раз вы запускаете свой код. Для небольших программ это может не иметь большого значения, но CFFI таким образом, они также лучше масштабируются и для более крупных проектов.
Как и ctypes, использование CFFI позволяет напрямую взаимодействовать только с библиотеками C. Использование библиотек C++ требует значительных усилий. В следующем разделе вы увидите инструмент привязки Python, ориентированный на C++.
PyBind11
PyBind11 использует совершенно другой подход к созданию привязок на Python. В дополнение к смещению акцента с C на C++, он также использует C++ для определения и сборки модуля, что позволяет использовать преимущества инструментов метапрограммирования в C++. Как и CFFI, привязки Python, сгенерированные из PyBind11, представляют собой полноценный модуль Python, который можно импортировать и использовать напрямую.
PyBind11 создан по образцу библиотеки Boost::Python и имеет аналогичный интерфейс. Однако он ограничивает свое использование C++11 и более поздними версиями, что позволяет ему упростить и ускорить работу по сравнению с Boost, который поддерживает все.
Как это устанавливается
В разделе Первые шаги документации PyBind11 рассказывается о том, как загрузить и создать тестовые примеры для PyBind11. Хотя это, по-видимому, не является обязательным, выполнение этих шагов обеспечит вам правильную настройку инструментов C++ и Python.
Примечание: В большинстве примеров для PyBind11 используется cmake,, который является прекрасным инструментом для создания проектов на C и C++. Однако для этой демонстрации вы продолжите использовать инструмент invoke, который следует инструкциям в разделе Создание вручную документации.
Вы захотите установить этот инструмент в свою виртуальную среду:
$ python3 -m pip install pybind11
PyBind11 представляет собой библиотеку со всеми заголовками, аналогичную большей части Boost. Это позволяет pip установить исходный код библиотеки на C++ непосредственно в вашу виртуальную среду.
Вызов функции
Прежде чем приступить к работе, пожалуйста, обратите внимание, что вы используете другой исходный файл C++, cppmult.cpp, вместо файла C, который вы использовали в предыдущих примерах. Функция, по сути, одна и та же в обоих языках.
Запись привязок
Аналогично CFFI, вам нужно создать некоторый код, указывающий инструменту, как создавать ваши привязки на Python. В отличие от CFFI, этот код будет написан на C++, а не на Python. К счастью, требуется минимальное количество кода:
// pybind11_wrapper.cpp
#include <pybind11/pybind11.h>
#include <cppmult.hpp>
PYBIND11_MODULE(pybind11_example, m) {
m.doc() = "pybind11 example plugin"; // Optional module docstring
m.def("cpp_function", &cppmult, "A function that multiplies two numbers");
}
Давайте рассмотрим это по частям, поскольку PyBind11 содержит много информации в нескольких строках.
Первые две строки содержат файл pybind11.h и заголовочный файл для вашей библиотеки C++, cppmult.hpp. После этого у вас будет макрос PYBIND11_MODULE. Это расширяется до блока кода на C++, который хорошо описан в PyBind11 источнике:
Этот макрос создает точку входа, которая будет вызвана, когда интерпретатор Python импортирует модуль расширения. Имя модуля указывается в качестве первого аргумента и не должно быть заключено в кавычки. Второй аргумент макроса определяет переменную типа
py::module, которая может быть использована для инициализации модуля. (Источник)
что это значит для вас заключается в том, что, например, вы создаете модуль, называемый pybind11_example и что остальная часть кода будет использовать m Название py::module объект. В следующей строке, внутри определяемой вами функции C++, вы создаете строку документации для модуля. Хотя это необязательно, это приятный штрих, позволяющий сделать ваш модуль более питоническим.
Наконец, у вас есть вызов m.def(). Это определит функцию, которая будет экспортироваться с помощью ваших новых привязок Python, то есть она будет видна из Python. В этом примере вы передаете три параметра:
cpp_functionэто экспортированное имя функции, которую вы будете использовать в Python. Как видно из этого примера, оно не обязательно должно совпадать с именем функции C++.&cppmultпринимает адрес экспортируемой функции."A function..."это необязательная строка документации для функции.
Теперь, когда у вас есть код для привязок Python, взгляните на то, как вы можете встроить это в модуль Python.
Создайте привязки Python
Инструмент, который вы используете для создания привязок Python в PyBind11, - это сам компилятор C++. Возможно, вам потребуется изменить настройки по умолчанию для вашего компилятора и операционной системы.
Для начала вы должны создать библиотеку C++, для которой вы создаете привязки. Для примера, такого небольшого размера, вы могли бы создать библиотеку cppmult непосредственно в библиотеке привязок Python. Однако для большинства реальных примеров у вас будет уже существующая библиотека, которую вы хотите обернуть, поэтому вы создадите библиотеку cppmult отдельно. Сборка - это стандартный вызов компилятора для создания общей библиотеки:
# tasks.py
invoke.run(
"g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC cppmult.cpp "
"-o libcppmult.so "
)
Выполнение этого действия с помощью invoke build-cppmult приводит к libcppmult.so:
$ invoke build-cppmult
==================================================
= Building C++ Library
* Complete
С другой стороны, сборка для привязок Python требует некоторых особых деталей:
1# tasks.py
2invoke.run(
3 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC "
4 "`python3 -m pybind11 --includes` "
5 "-I /usr/include/python3.7 -I . "
6 "{0} "
7 "-o {1}`python3.7-config --extension-suffix` "
8 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name)
9)
Давайте пройдемся по этому вопросу построчно. Строка 3 содержит довольно стандартные флаги компилятора C++, которые указывают на несколько деталей, в том числе на то, что вы хотите, чтобы все предупреждения перехватывались и рассматривались как ошибки, что вам нужна общая библиотека и что вы используете C++11.
Строка 4 - это первый шаг к волшебству. Он вызывает модуль pybind11, чтобы тот создал правильные пути include для PyBind11. Вы можете запустить эту команду непосредственно в консоли, чтобы посмотреть, что она делает:
$ python3 -m pybind11 --includes
-I/home/jima/.virtualenvs/realpython/include/python3.7m
-I/home/jima/.virtualenvs/realpython/include/site/python3.7
Ваши выходные данные должны быть похожими, но показывать разные пути.
В строке 5 вашего вызова компиляции вы можете видеть, что вы также добавляете путь к Python dev includes. Хотя рекомендуется, чтобы вы не использовали ссылки на саму библиотеку Python, исходному коду требуется некоторый код из Python.h, чтобы сработало его волшебство. К счастью, код, который он использует, довольно стабилен в разных версиях Python.
В строке 5 также используется -I . для добавления текущего каталога в список путей include. Это позволяет разрешить строку #include <cppmult.hpp> в вашем коде-оболочке.
В строке 6 указывается имя вашего исходного файла, которое равно pybind11_wrapper.cpp. Затем, в строке 7 вы увидите, что происходит еще больше волшебства сборки. В этой строке указывается имя выходного файла. У Python есть несколько конкретных идей по именованию модулей, которые включают версию Python, архитектуру машины и другие детали. Python также предоставляет инструмент, помогающий в этом, который называется python3.7-config:
$ python3.7-config --extension-suffix
.cpython-37m-x86_64-linux-gnu.so
Вам может потребоваться изменить команду, если вы используете другую версию Python. Ваши результаты, скорее всего, изменятся, если вы используете другую версию Python или другую операционную систему.
Последняя строка вашей команды сборки, строка 8, указывает компоновщику на библиотеку libcppmult, которую вы создали ранее. В разделе rpath компоновщику предлагается добавить информацию в общую библиотеку, чтобы помочь операционной системе найти libcppmult во время выполнения. Наконец, вы заметите, что эта строка отформатирована с помощью cpp_name и extension_name. Вы будете использовать эту функцию снова, когда будете создавать свой модуль привязок Python с помощью Cython в следующем разделе.
Запустите эту команду, чтобы создать свои привязки:
$ invoke build-pybind11
==================================================
= Building C++ Library
* Complete
==================================================
= Building PyBind11 Module
* Complete
Вот и все! Вы создали свои привязки к Python с помощью PyBind11. Пришло время протестировать это!
Вызов вашей функции
Как и в примере CFFI, приведенном выше, после того, как вы выполнили тяжелую работу по созданию привязок на Python, вызов вашей функции выглядит как обычный код на Python:
# pybind11_test.py
import pybind11_example
if __name__ == "__main__":
# Sample data for your call
x, y = 6, 2.3
answer = pybind11_example.cpp_function(x, y)
print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
Поскольку вы использовали pybind11_example в качестве имени вашего модуля в макросе PYBIND11_MODULE, это имя вы импортируете. В вызове m.def() вы сказали PyBind11 экспортировать функцию cppmult как cpp_function, так что это то, что вы используете для ее вызова из Python.
Вы также можете протестировать это с помощью invoke:
$ invoke test-pybind11
==================================================
= Testing PyBind11 Module
In cppmul: int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 13.8
Вот как выглядит PyBind11. Далее вы увидите, когда и почему PyBind11 является подходящим инструментом для этой работы.
Сильные и слабые стороны
PyBind11 ориентирован на C++, а не на C, что отличает его от ctypes и CFFI. У него есть несколько особенностей, которые делают его весьма привлекательным для библиотек C++:
- Он поддерживает класса.
- Он обрабатывает полиморфные подклассы.
- Это позволяет вам добавлять динамические атрибуты к объектам из Python и многих других инструментов, что было бы довольно сложно сделать с помощью инструментов на C, которые вы рассмотрели.
Как бы то ни было, вам необходимо выполнить некоторую настройку, чтобы запустить PyBind11. Выполнение правильной установки и сборки может быть немного сложным, но как только это будет сделано, все будет выглядеть довольно надежно. Кроме того, PyBind11 требует, чтобы вы использовали как минимум C++11 или новее. Вряд ли это будет серьезным ограничением для большинства проектов, но для вас это может быть важным фактором.
Наконец, дополнительный код, который вам нужно написать для создания привязок Python, написан на C++, а не на Python. Это может быть проблемой для вас, а может и не быть, но она отличается от других инструментов, которые вы здесь рассматривали. В следующем разделе вы перейдете к Cython, в котором используется совершенно иной подход к этой проблеме.
Cython
Подход, который Cython используется для создания привязок на Python, использует язык, подобный Python, для определения привязок и затем генерирует код на C или C++, который может быть скомпилирован в модуль. Существует несколько методов создания привязок Python с помощью Cython. Наиболее распространенным является использование setup из distutils. В этом примере вы будете использовать инструмент invoke, который позволит вам играть именно с теми командами, которые выполняются.
Как это устанавливается
Cython это модуль Python, который может быть установлен в вашу виртуальную среду из PyPI:
$ python3 -m pip install cython
Опять же, если вы установили файл requirements.txt в свою виртуальную среду, то он уже будет там. Вы можете получить копию файла requirements.txt, перейдя по ссылке ниже:
Получите пример кода: Нажмите здесь, чтобы получить пример кода, который вы будете использовать для изучения привязок Python в этом руководстве.
Это должно подготовить вас к работе с Cython!
Вызов функции
Чтобы создать свои привязки Python с помощью Cython, вам необходимо выполнить шаги, аналогичные тем, которые вы использовали для CFFI и PyBind11. Вы напишете привязки, создадите их, а затем запустите код на Python для их вызова. Cython может поддерживать как C, так и C++. Для этого примера вы будете использовать библиотеку cppmult, которую вы использовали для примера PyBind11, приведенного выше.
Запишите привязки
Наиболее распространенной формой объявления модуля в Cython является использование .pyx файла:
1# cython_example.pyx
2""" Example cython interface definition """
3
4cdef extern from "cppmult.hpp":
5 float cppmult(int int_param, float float_param)
6
7def pymult( int_param, float_param ):
8 return cppmult( int_param, float_param )
Здесь есть два раздела:
- В строках 3 и 4 указано
Cython, что вы используетеcppmult()изcppmult.hpp. - Строки 6 и 7 создают функцию-оболочку
pymult()для вызоваcppmult().
Используемый здесь язык представляет собой особую смесь C, C++ и Python. Однако разработчикам Python он покажется довольно знакомым, поскольку цель состоит в том, чтобы упростить процесс.
В первом разделе с cdef extern... указано Cython, что приведенные ниже описания функций также находятся в cppmult.hpp файле. Это полезно для обеспечения того, чтобы ваши привязки к Python создавались на основе тех же объявлений, что и в вашем коде на C++. Второй раздел выглядит как обычная функция Python — потому что так оно и есть! В этом разделе создается функция Python, имеющая доступ к функции C++ cppmult.
Теперь, когда вы определили привязки Python, пришло время их создать!
Создайте привязки Python
Процесс сборки для Cython имеет сходство с тем, который вы использовали для PyBind11. Сначала вы запускаете Cython для файла .pyx, чтобы сгенерировать файл .cpp. Как только вы это сделаете, вы скомпилируете его с помощью той же функции, которую вы использовали для PyBind11:
1# tasks.py
2def compile_python_module(cpp_name, extension_name):
3 invoke.run(
4 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC "
5 "`python3 -m pybind11 --includes` "
6 "-I /usr/include/python3.7 -I . "
7 "{0} "
8 "-o {1}`python3.7-config --extension-suffix` "
9 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name)
10 )
11
12def build_cython(c):
13 """ Build the cython extension module """
14 print_banner("Building Cython Module")
15 # Run cython on the pyx file to create a .cpp file
16 invoke.run("cython --cplus -3 cython_example.pyx -o cython_wrapper.cpp")
17
18 # Compile and link the cython wrapper library
19 compile_python_module("cython_wrapper.cpp", "cython_example")
20 print("* Complete")
Для начала запустите cython в вашем файле .pyx. В этой команде можно использовать несколько параметров:
--cplusуказывает компилятору сгенерировать файл C++ вместо файла C.-3переключаетCythonдля генерации синтаксиса Python 3 вместо синтаксиса Python 2.-o cython_wrapper.cppуказывает имя создаваемого файла.
Как только файл C++ сгенерирован, вы используете компилятор C++ для генерации привязок Python, точно так же, как вы это делали для PyBind11. Обратите внимание, что вызов для создания дополнительных путей include с помощью инструмента pybind11 по-прежнему выполняется в этой функции. Здесь это ничему не помешает, поскольку вашему источнику они не понадобятся.
Выполнение этой задачи в invoke приводит к следующему результату:
$ invoke build-cython
==================================================
= Building C++ Library
* Complete
==================================================
= Building Cython Module
* Complete
Вы можете видеть, что он создает библиотеку cppmult, а затем создает модуль cython, чтобы обернуть ее. Теперь у вас есть Cython привязки Python. (Попробуйте быстро произнести , что ...) Пришло время проверить это!
Вызов вашей функции
Код на Python для вызова ваших новых привязок на Python очень похож на тот, который вы использовали для тестирования других модулей:
1# cython_test.py
2import cython_example
3
4# Sample data for your call
5x, y = 6, 2.3
6
7answer = cython_example.pymult(x, y)
8print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
Строка 2 импортирует ваш новый модуль привязок Python, и вы вызываете pymult() в строке 7. Помните, что файл .pyx содержит оболочку Python для cppmult() и переименован в pymult. Использование invoke для запуска теста приводит к следующему результату:
$ invoke test-cython
==================================================
= Testing Cython Module
In cppmul: int: 6 float 2.3 returning 13.8
In Python: int: 6 float 2.3 return val 13.8
Вы получите тот же результат, что и раньше!
Сильные и слабые стороны
Cython это относительно сложный инструмент, который может предоставить вам глубокий уровень контроля при создании привязок Python для C или C++. Хотя здесь вы не рассматривали это подробно, в нем представлен метод написания кода на Python, который вручную управляет GIL, что может значительно ускорить решение определенных типов проблем.
Однако этот язык, похожий на Python, не совсем Python, поэтому, когда вы набираете скорость в понимании того, какие части C и Python подходят друг другу, возникает небольшая проблема с обучением.
Другие решения
Изучая это руководство, я наткнулся на несколько различных инструментов и опций для создания привязок Python. Хотя я ограничил этот обзор некоторыми наиболее распространенными опциями, я наткнулся на несколько других инструментов. Приведенный ниже список неполон. Это всего лишь пример других возможностей, если один из вышеперечисленных инструментов не подходит для вашего проекта.
PyBindGen
PyBindGen генерирует привязки Python для C или C++ и написан на Python. Он предназначен для создания удобочитаемого кода на C или C++, что должно упростить проблемы с отладкой. Было неясно, обновлялся ли он недавно, поскольку в документации Python 3.4 указан как последняя протестированная версия. Однако в течение последних нескольких лет выпускались ежегодные выпуски.
Boost.Python
Boost.Python имеет интерфейс, аналогичный PyBind11, который вы видели выше. Это не совпадение, поскольку PyBind11 была основана на этой библиотеке! Boost.Python полностью написана на C++ и поддерживает большинство, если не все, версии C++ на большинстве платформ. Напротив, PyBind11 ограничивается современным C++.
SIP
SIP это набор инструментов для создания привязок на Python, который был разработан для проекта PyQt. Он также используется проектом wxPython для создания своих привязок. В нем есть инструмент генерации кода и дополнительный модуль Python, который предоставляет функции поддержки сгенерированного кода.
Cppyy
cppyy это интересный инструмент, дизайн которого немного отличается от того, что вы видели до сих пор. По словам автора пакета:
“Первоначальная идея cppyy (восходящая к 2001 году) заключалась в том, чтобы предоставить программистам на Python, живущим в мире C++, доступ к этим пакетам C++, не касаясь C++ напрямую (или ожидая, пока разработчики C++ придут в себя и предоставят привязки).” ( Источник)
Shiboken
Shiboken это инструмент для создания привязок на Python, разработанный для проекта PySide, связанного с проектом Qt . Хотя он был разработан как инструмент для этого проекта, в документации указано, что он не привязан ни к Qt, ни к PySide и может использоваться для других проектов.
SWIG
SWIG это инструмент, отличный от любого другого, перечисленного здесь. Это универсальный инструмент, используемый для создания привязок к программам на C и C++ для многих других языков , не только для Python. Эта возможность создавать привязки для разных языков может быть весьма полезной в некоторых проектах. Это, конечно, сопряжено с определенными затратами в плане сложности.
Заключение
Поздравляю! Теперь вы ознакомились с несколькими различными вариантами создания привязок Python. Вы узнали о сортировке данных и проблемах, которые необходимо учитывать при создании привязок. Вы уже видели, что нужно для вызова функции C или C++ из Python с помощью следующих инструментов:
ctypesCFFIPyBind11Cython
Теперь вы знаете, что, в то время как ctypes позволяет загружать DLL или общую библиотеку напрямую, остальные три инструмента делают дополнительный шаг, но все равно создают полноценный модуль Python. В качестве бонуса вы также немного поиграли с инструментом invoke для запуска задач командной строки из Python. Вы можете получить весь код, который вы видели в этом руководстве, перейдя по ссылке ниже:
Получите пример кода: Нажмите здесь, чтобы получить пример кода, который вы будете использовать для изучения привязок Python в этом руководстве.
Теперь выбирайте свой любимый инструмент и начинайте создавать привязки на Python! Особая благодарность Лоику Домейну за дополнительный технический обзор этого руководства.
<статус завершения article-slug="python-привязки-обзор" class="btn-group mb-0" data-api-статья-закладка-url="/api/v1/articles/python-привязки-обзор/закладка/" данные-api-статья-статус завершения-url="/api/v1/articles/python-привязки-обзор/завершение_статус/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0APython-привязки: Вызов C или C++ из Python" email-subject="Статья о Python для вас" twitter-text="Интересная статья о #Python от @realpython:" url="https://realpython.com/python-bindings-overview /" url-title="Привязки к Python: вызов C или C++ из Python"> кнопка "поделиться">
Back to Top