Создание расширения C++ для Python
Область применения:Visual Studio Visual Studio
для Mac
Visual Studio Code
Модули, написанные на C++ (или C), обычно используются для расширения возможностей интерпретатора Python. Они также обеспечивают доступ к низкоуровневым возможностям операционной системы.
Модули можно разделить на три следующих основных типа.
- Модули ускорения. Так как Python является интерпретируемым языком, вы можете писать модули ускорителя на C++ для повышения производительности.
- Модули-оболочки. Эти модули открывают существующие интерфейсы C/C++ для кода Python или предоставляют адаптированный API, который удобно использовать в Python.
- Модули низкоуровневого системного доступа. Эти модули создаются для доступа к низкоуровневым функциям среды выполнения
CPython
, операционной системы или базового оборудования.
В этой статье рассматривается создание модуля расширения C++ для CPython
, который вычисляет гиперболический тангенс и вызывает его из кода Python. Подпрограмма реализована сначала на языке Python, чтобы продемонстрировать относительный прирост производительности по сравнению с реализацией той же подпрограммы на C++.
В этой статье также показаны два способа сделать расширение C++ доступным в Python:
- использование стандартных расширений
CPython
, как описано в документации по Python; - использование PyBind11 (рекомендуется для C++ 11 благодаря его простоте). Для обеспечения совместимости используйте какую-то из более актуальных версий Python.
Полный пример из этого руководства см. на GitHub по ссылке python-samples-vs-cpp-extension.
Необходимые компоненты
Visual Studio 2017 или более поздней версии с установленной рабочей нагрузкой разработки на Python. Эта рабочая нагрузка включает встроенные средства разработки Python, которые подключают рабочую нагрузку C++ и наборы инструментов, необходимые для собственных расширений.
Примечание.
При установке рабочей нагрузки Приложения для обработки и анализа данных и аналитические приложения среда Python и встроенные средства разработки Python устанавливаются по умолчанию.
Дополнительные сведения о параметрах установки см. в разделе Установка поддержки Python в Visual Studio. Если вы устанавливаете Python отдельно, обязательно выберите параметр Скачать отладочные символы в разделе Дополнительные параметры установщика. Этот параметр необходим, чтобы вы могли использовать отладку в смешанном режиме между кодом Python и машинным кодом.
Создание приложения Python
Создайте проект Python в Visual Studio, выбрав Файл>Создать>Проект.
Выполните поиск по ключевому слову Python, выберите шаблон Приложение Python, задайте имя и расположение, а затем нажмите кнопку OK.
В файл проекта с расширением .py вставьте следующий код. Чтобы испытать некоторые функции редактирования Python, попробуйте ввести этот код вручную.
Этот код вычислит гиперболический тангенс без использования математической библиотеки, что позволит вам ускорить работу встроенных расширений.
Совет
Сначала напишите код только на Python, а затем перепишите его на C++. Таким образом вам будет проще проверить правильность машинного кода.
from random import random from time import perf_counter COUNT = 500000 # Change this value depending on the speed of your computer DATA = [(random() - 0.5) * 3 for _ in range(COUNT)] e = 2.7182818284590452353602874713527 def sinh(x): return (1 - (e ** (-2 * x))) / (2 * (e ** -x)) def cosh(x): return (1 + (e ** (-2 * x))) / (2 * (e ** -x)) def tanh(x): tanh_x = sinh(x) / cosh(x) return tanh_x def test(fn, name): start = perf_counter() result = fn(DATA) duration = perf_counter() - start print('{} took {:.3f} seconds\n\n'.format(name, duration)) for d in result: assert -1 <= d <= 1, " incorrect values" if __name__ == "__main__": print('Running benchmarks with COUNT = {}'.format(COUNT)) test(lambda d: [tanh(x) for x in d], '[tanh(x) for x in d] (Python implementation)')
Чтобы просмотреть результаты, запустите программу, выбрав Отладка>Запуск без отладки или нажав клавиши CTRL+F5.
Вы можете изменять длительность выполнения тестов производительности, изменяя переменную
COUNT
. Для целей данного руководства задайте ей такое значение, чтобы тест производительности занимал примерно две секунды.Совет
При запуске тестов производительности всегда выбирайте в меню Отладка>Запуск без отладки. Это помогает избежать дополнительных временных затрат, возникающих при запуске кода в отладчике Visual Studio.
Создание основных проектов C++
Следуйте инструкциям в этом разделе, чтобы создать два идентичных проекта C++ с именами superfastcode и superfastcode2. Позднее вы будете использовать в этих проектах разные способы предоставления кода C++ в Python.
В обозревателе решений щелкните решение правой кнопкой мыши и последовательно выберите пункты Добавить>Новый проект. Решение Visual Studio может одновременно содержать проекты Python и C++ — это одно из преимуществ использования Visual Studio для Python.
Выполните поиск C++, выберите Пустой проект, укажите либо superfastcode для первого проекта, либо superfastcode2 для второго проекта, а затем нажмите кнопку OK.
Совет
Или, если вы установили собственные средства разработки Python в Visual Studio, можете начать с шаблона "Модуль расширения Python". В этом шаблоне уже имеется большая часть описанных здесь возможностей.
Но в этом пошаговом руководстве начало работы с пустого проекта позволяет шаг за шагом продемонстрировать создание модуля расширения. Ознакомившись с процессом, в дальнейшем вы сможете использовать этот шаблон для экономии времени при написании собственных расширений.
Чтобы создать файл C++ в новом проекте, щелкните правой кнопкой мыши узел Исходные файлы и выберите Добавить>Новый элемент.
Выберите Файл C++, задайте для него имя module.cpp, а затем нажмите кнопку OK.
Важно!
Файл с расширением .cpp нужен, чтобы активировать страницы свойств C++ в последующих шагах.
На главной панели инструментов выберите в раскрывающемся меню одну из следующих двух конфигураций:
- Для 64-разрядной среды выполнения Python активируйте конфигурацию x64.
- Для 32-разрядной среды выполнения Python следует активировать конфигурацию Win32.
В Обозревателе решений щелкните правой кнопкой мыши проект C++, выберите пункт Свойства, а затем выполните следующие действия.
a. В поле Конфигурация укажите Активная (отладка).
b. В поле Платформа введите либо Активная (x64), либо Активная (Win32) в зависимости от выбора, сделанного на предыдущем шаге.Примечание.
При создании собственных проектов вам понадобится настроить конфигурации и отладки, и выпуска. В этом уроке вы настроите только конфигурацию отладки и установите в ней использование сборки выпуска CPython. Эта конфигурация отключает некоторые функции отладки среды выполнения C++, в том числе утверждения. Для использования двоичных файлов отладки CPython (python_d.exe) требуются другие параметры.
Задайте свойства, как показано в следующей таблице.
Tab Свойство Стоимость Общие сведения Имя цели Укажите имя модуля, которое будет использоваться для ссылки на этот модуль из Python в инструкциях from...import
. Это же имя будет использоваться в C++ при определении модуля для Python. Чтобы применить имя проекта в качестве имени модуля, сохраните значение по умолчанию $<(ProjectName)>. Дляpython_d.exe
добавьте_d
в конец файла.Дополнительно>Расширение целевого файла .pyd Проект по умолчанию>Тип конфигурации Динамическая библиотека (.dll) C/C++>Общие Дополнительные каталоги включаемых файлов Добавьте подходящую для вашей установки папку include Python (например, c:\Python36\include
).C/C++>Препроцессор Определения препроцессора При наличии этого свойства измените значение _DEBUG на NDEBUG для соответствия неотладочной версии CPython. Если вы используете python_d.exe, оставьте это значение без изменений. C/C++>Создание кода Библиотека времени выполнения Выберите Многопоточная DLL (/MD) для соответствия не отладочной версии CPython. Если вы используете python_d.exe, оставьте значение Многопоточная DLL для отладки (/MDd). Базовые проверки среды выполнения По умолчанию Компоновщик>Общие Дополнительные каталоги библиотек Добавьте подходящую для вашей установки папку Python libs с файлами .lib (например c:\Python36\libs). Убедитесь, что указали папку libs, содержащую файлы .lib, но не папку Lib, содержащую файлы .py. Примечание.
Если вы не видите вкладку C/C++ в свойствах проекта, значит, в проекте нет файлов, определенных как исходные файлы C/C++. Такая ситуация может возникнуть, если вы создали исходный файл без расширения .c или .cpp.
Например, если в диалоговом окне создания элемента вы случайно введете module.coo вместо module.cpp, Visual Studio создаст файл, но не задаст для него тип Код C/C, что требуется для активации вкладки свойств C/C++. Такая неправильная идентификация сохранится, даже если вы переименуете файл и добавите расширение .cpp.
Чтобы установить правильный тип файла, в Обозревателе решений щелкните файл правой кнопкой мыши и выберите пункт Свойства. Затем в качестве типа файлавыберите Код C/C++.
Нажмите ОК.
Чтобы протестировать конфигурации (как для отладки, так и для выпуска), щелкните правой кнопкой мыши проект C++ и выберите пункт Сборка.
Обратите внимание, что файлы .pyd находятся в папке solution в подпапках Debug и Release, а не в самой папке проекта C++.
Добавьте следующий код в файл module.cpp проекта С++.
#include <Windows.h> #include <cmath> const double e = 2.7182818284590452353602874713527; double sinh_impl(double x) { return (1 - pow(e, (-2 * x))) / (2 * pow(e, -x)); } double cosh_impl(double x) { return (1 + pow(e, (-2 * x))) / (2 * pow(e, -x)); } double tanh_impl(double x) { return sinh_impl(x) / cosh_impl(x); }
Еще раз выполните сборку проекта C++, чтобы убедиться в правильности кода.
Если вы еще этого не сделали, повторите предыдущие действия, чтобы создать второй проект с именем superfastcode2 и тем же содержимым.
Преобразование проекта C++ в расширения для Python
Чтобы преобразовать DLL-библиотеку C++ в расширение для Python, сначала нужно изменить экспортируемые методы для взаимодействия с типами Python. После этого добавьте функцию, экспортирующую модуль, а также определения методов модуля.
В последующих разделах объясняется, как выполнить эти действия с помощью расширений CPython и PyBind11.
Использование расширений CPython
Дополнительные сведения о коде, приведенном в этом разделе, см. в справочном руководстве по API Python/C, в частности на странице Объекты модуля. Не забудьте выбрать версию Python в раскрывающемся списке в правом верхнем углу.
В верхней части файла module.cpp включите Python.h.
#include <Python.h>
Измените метод
tanh_impl
, чтобы он принимал и возвращал типы Python (PyObject*
).PyObject* tanh_impl(PyObject* /* unused module reference */, PyObject* o) { double x = PyFloat_AsDouble(o); double tanh_x = sinh_impl(x) / cosh_impl(x); return PyFloat_FromDouble(tanh_x); }
Добавьте структуру, определяющую способ представления функции
tanh_impl
C++ для Python:static PyMethodDef superfastcode_methods[] = { // The first property is the name exposed to Python, fast_tanh // The second is the C++ function with the implementation // METH_O means it takes a single PyObject argument { "fast_tanh", (PyCFunction)tanh_impl, METH_O, nullptr }, // Terminate the array with an object containing nulls. { nullptr, nullptr, 0, nullptr } };
Добавьте структуру, которая определяет модуль так, как вы хотите ссылаться на него в своем коде Python, в частности при использовании инструкции
from...import
.Имя, которое импортируется в этом коде, должно соответствовать значению в свойствах проекта в разделе Свойства конфигурации>Общее>Имя целевого объекта.
В приведенном ниже примере имя модуля
"superfastcode"
означает, что вы можете использоватьfrom superfastcode import fast_tanh
в Python, так какfast_tanh
определено вsuperfastcode_methods
. Внутренние для проекта C++ имена файлов, такие как module.cpp, являются несущественными.static PyModuleDef superfastcode_module = { PyModuleDef_HEAD_INIT, "superfastcode", // Module name to use with Python import statements "Provides some functions, but faster", // Module description 0, superfastcode_methods // Structure that defines the methods of the module };
Добавьте метод, вызываемый Python при загрузке модуля. Он должен иметь имя
PyInit_<module-name>
, где <module-name> точно соответствует значению свойства Общие>Целевое имя проекта C++. То есть оно должно соответствовать имени файла с расширением .pyd, созданного проектом.PyMODINIT_FUNC PyInit_superfastcode() { return PyModule_Create(&superfastcode_module); }
Еще раз выполните сборку проекта C++, чтобы проверить код. Если возникнут ошибки, см. раздел Устранение неполадок.
Использование PyBind11
Если вы выполнили действия из предыдущего раздела, вы наверняка заметили, что использовали много стандартного кода для создания необходимых структур модулей для кода C++. PyBind11 упрощает этот процесс с помощью макросов в файле заголовка C++, которые позволяют получить тот же результат с гораздо меньшим объемом кода.
Дополнительные сведения о коде в этом разделе см. в разделе Основы PyBind11.
Установите PyBind11 с помощью pip:
pip install pybind11
илиpy -m pip install pybind11
.Вы также можете установить PyBind11 с помощью окна "Окружения Python", а затем использовать его команду Открыть в PowerShell для выполнения следующего шага.
В том же терминале выполните
python -m pybind11 --includes
илиpy -m pybind11 --includes
.Это действие выводит список путей, которые следует добавить в свойство C/C++>Общие>Дополнительные каталоги включаемых файлов вашего проекта. Обязательно удалите префикс
-I
, если он присутствует.В верхнюю часть нового файла module.cpp, который не содержит изменения из предыдущего раздела, включите pybind11.h.
#include <pybind11/pybind11.h>
В нижней части module.cpp с помощью макроса
PYBIND11_MODULE
определите точку входа в функцию C++.namespace py = pybind11; PYBIND11_MODULE(superfastcode2, m) { m.def("fast_tanh2", &tanh_impl, R"pbdoc( Compute a hyperbolic tangent of a single argument expressed in radians. )pbdoc"); #ifdef VERSION_INFO m.attr("__version__") = VERSION_INFO; #else m.attr("__version__") = "dev"; #endif }
Выполните сборку проекта C++, чтобы проверить код. При возникновении ошибок см. способы их устранения в следующем разделе "Устранение ошибок компиляции".
Устранение ошибок компиляции
Модуль C++ может не компилироваться по следующим причинам.
Ошибка: не удается обнаружить Python.h (E1696: не удается открыть исходный файл Python.h и/или C1083: не удается открыть включаемый файл: Python.h — не существует такого файла или каталога)
Решение: проверьте, что путь в разделе C/C++>Общие>Дополнительные каталоги включаемых файлов в свойствах проекта указывает на папку include установки Python. См. шаг 6 в разделе Создание основного проекта C++. Дополнительные сведения о доступе к сведениям о конфигурации установки Python см . в документации по Python.
Ошибка: не удается обнаружить библиотеки Python
Решение: проверьте, что путь в разделе Компоновщик>Общие>Дополнительные каталоги библиотек в свойствах проекта указывает на папку libs установки Python. См. шаг 6 в разделе Создание основного проекта C++. Дополнительные сведения о доступе к сведениям о конфигурации установки Python см . в документации по Python.
Ошибки компоновщика, связанные с решением целевой архитектуры: измените архитектуру проекта целевого объекта C++ на соответствие архитектуре установки Python. Например, если вы хотите использовать в проекте C++ версию Win32, но у вас установлена версия Python x64, измените проект C++ для работы с версией х64.
Тестирование кода и сравнение результатов
Теперь, когда библиотека DLL структурирована как расширения Python, можно ссылаться на них из проекта Python, импортировать модули и использовать их методы.
Предоставление доступа к библиотеке DLL для Python
Библиотеку DLL можно сделать доступной для Python несколькими способами. Покажем два метода, которые рекомендуется рассмотреть.
Первый метод работает, если проект Python и проект C++ находятся в одном решении. Выполните следующие действия.
В Обозревателе решений щелкните правой кнопкой мыши узел Ссылки в вашем проекте Python, а затем выберите команду Добавить ссылку.
В открывшемся диалоговом окне перейдите на вкладку Проекты, выберите проекты superfastcode и superfastcode2 и нажмите кнопку OK.
В другом методе устанавливается модуль в вашей среде Python, что делает этот модуль доступным и для других проектов Python. Дополнительные сведения см. в документации по проекту setuptools. Выполните следующие действия.
Создайте в проекте C++ файл с именем setup.py, щелкнув проект правой кнопкой мыши и выбрав пункт Добавить>Новый элемент.
Выберите Файл C++ (. cpp), назначьте этому файлу имя setup.py и нажмите кнопку OK.
Если файлу назначить имя с расширением .py, это позволяет Visual Studio распознать его как файл Python, несмотря на использование шаблона файла C++.
Когда файл откроется в редакторе, вставьте в него следующий код в зависимости от метода расширения.
Для расширений
CPython
(проект superfastcode)from setuptools import setup, Extension sfc_module = Extension('superfastcode', sources = ['module.cpp']) setup( name='superfastcode', version='1.0', description='Python Package with superfastcode C++ extension', ext_modules=[sfc_module] )
Для
PyBind11
(проект superfastcode2)from setuptools import setup, Extension import pybind11 cpp_args = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7'] sfc_module = Extension( 'superfastcode2', sources=['module.cpp'], include_dirs=[pybind11.get_include()], language='c++', extra_compile_args=cpp_args, ) setup( name='superfastcode2', version='1.0', description='Python package with superfastcode2 C++ extension (PyBind11)', ext_modules=[sfc_module], )
В проекте C++ создайте второй файл с именем pyproject.toml и вставьте в него следующий код.
[build-system] requires = ["setuptools", "wheel", "pybind11"] build-backend = "setuptools.build_meta"
Чтобы создать расширение, щелкните правой кнопкой мыши открытую вкладку pyproject.toml и выберите Копировать полный путь. Вы удалите имя pyproject.toml из пути, прежде чем использовать его.
В Обозревателе решений щелкните правой кнопкой мыши активную среду Python и выберите пункт Управление пакетами Python.
Совет
Если пакет уже установлен, он появится в списке. Прежде чем продолжить, щелкните значок X, чтобы удалить его.
В поле поиска вставьте скопированный путь, удалите pyproject.toml в конце пути и нажмите клавишу ВВОД, чтобы установить модуль из этого каталога.
Совет
Если установка завершается неудачно из-за ошибки, связанной с разрешениями, добавьте в конец --user и попробуйте выполнить команду еще раз.
Вызов библиотеки DLL из Python
После того как вы сделаете библиотеки DLL доступными для Python, как описано в предыдущем разделе, можно вызывать функции superfastcode.fast_tanh
и superfastcode2.fast_tanh2
из кода Python и сравнивать их эффективность с реализацией Python. Чтобы вызвать библиотеку DLL, выполните следующие действия.
Добавьте следующие строки в файл .py, чтобы вызвать методы, экспортированные из библиотек DLL, и отобразить их выходные данные.
from superfastcode import fast_tanh test(lambda d: [fast_tanh(x) for x in d], '[fast_tanh(x) for x in d] (CPython C++ extension)') from superfastcode2 import fast_tanh2 test(lambda d: [fast_tanh2(x) for x in d], '[fast_tanh2(x) for x in d] (PyBind11 C++ extension)')
Запустите программу Python, выбрав в меню Отладка>Запуск без отладки или нажав клавиши CTRL + F5.
Примечание.
Если команда Запуск без отладки отключена, в Обозревателе решений щелкните правой кнопкой мыши проект Python и выберите команду Назначить запускаемым проектом.
Убедитесь, что подпрограммы C++ выполняются примерно в 5–20 раз быстрее, чем реализация на Python. Обычно выводится следующий результат.
Running benchmarks with COUNT = 500000 [tanh(x) for x in d] (Python implementation) took 0.758 seconds [fast_tanh(x) for x in d] (CPython C++ extension) took 0.076 seconds [fast_tanh2(x) for x in d] (PyBind11 C++ extension) took 0.204 seconds
Попробуйте увеличить значение переменной
COUNT
, чтобы разница стала еще очевиднее.Кроме того, отладочная сборка модуля C++ выполняется медленнее, чем сборка выпуска, так как отладочная сборка менее оптимизирована и содержит разные проверки ошибок. Вы можете переключаться между этими конфигурациями для сравнения, но не забудьте вернуться и обновить заданные ранее свойства для конфигурации выпуска.
В выходных данных вы можете увидеть, что расширение PyBind11 не такое быстрое, как расширение CPython, хотя оно все равно должно работать быстрее, чем прямая реализация на Python. Это различие в основном связано с тем, что мы использовали вызов METH_O
, который не поддерживает несколько параметров, имен параметров или аргументов ключевых слов. PyBind11 формирует несколько более сложный код, чтобы предоставить вызывающим субъектам интерфейс, более похожий на Python. Но так как тестовый код вызывает функцию 500 000 раз, в результате эти дополнительные временные затраты могут сильно возрасти!
Чтобы сократить временные затраты, можно переместить цикл for
в машинный код. Этот подход предполагает использование протокола итератора (или типа py::iterable
из PyBind11 в качестве параметра функции) для обработки каждого элемента. Удаление повторяющихся переходов между Python и C++ является эффективным способом сокращения времени, затрачиваемого на обработку последовательности.
Устранение ошибок импорта
Если при попытке импорта модуля вы получаете сообщение ImportError
, для разрешения этой проблемы можно использовать один из следующих способов.
При сборке с помощью ссылки на проект убедитесь, что свойства проекта C++ соответствуют среде Python, активированной для проекта Python, особенно каталоги Include и Library.
Убедитесь, что ваш выходной файл имеет имя superfastcode.pyd. Файл с любым другим именем или расширением не будет импортирован.
Если вы установили модуль с помощью файла setup.py, убедитесь, что вы выполнили команду pip в среде Python, активированной для вашего проекта Python. При развертывании среды Python в обозревателе решений должна отображаться запись для superfastcode.
Отладка кода C++
Visual Studio поддерживает совместную отладку кода на Python и C++. В этом разделе вы пошагово выполните процесс с использованием проекта superfastcode. Этот процесс такой же, как для проекта superfastcode2.
В Обозревателе решений щелкните проект Python правой кнопкой мыши, выберите Свойства, откройте вкладку Отладка и выберите Отладка>Разрешить отладку машинного кода.
Совет
При включении отладки машинного кода окно вывода Python может немедленно закрываться сразу после завершения программы без обычной паузы с сообщением Для продолжения нажмите любую клавишу.
Решение: чтобы после включения отладки машинного кода принудительно приостановить выполнение программы, добавьте параметр
-i
в поле Запуск>Аргументы интерпретатора на вкладке Отладка. Этот аргумент переводит интерпретатор Python в интерактивный режим после выполнения кода, и для закрытия окна интерпретатора необходимо будет нажать клавиши CTRL+Z, а затем ВВОД.Кроме того, если вы не против изменения кода Python, можно добавить в конец программы операторы
import os
иos.system("pause")
. Этот код дублирует исходный запрос с приостановкой.Чтобы сохранить изменения свойств, выберите пункт меню Файл>Сохранить.
В панели инструментов Visual Studio установите для конфигурации сборки режим Отладка.
Так как в отладчике код обычно выполняется дольше, вы, возможно, захотите уменьшить значение переменной
COUNT
в файле .py приблизительно в пять раз. Например, вместо 500000 задать 100000.В коде C++ установите точку останова в первой строке метода
tanh_impl
, а затем запустите отладчик, нажав клавишу F5 или выбрав в меню Отладка>Начать отладку.Когда выполнение доходит до кода точки останова, отладчик останавливается. Если точка останова не сработает, проверьте, выбрана ли конфигурация отладки и сохранен ли проект (при запуске отладчика он не сохраняется автоматически).
В точке останова вы можете пошагово проходить по коду C++, проверять переменные и т. д. Дополнительные сведения об этих функциях см. в разделе Одновременная отладка Python и C++.
Альтернативные подходы
Существуют различные способы для создания расширений Python, приведенные в таблице ниже. Способы CPython
и PyBind11
, указанные в первых двух строках, рассматриваются в этой статье.
Подход | Vintage | Представители |
---|---|---|
Модули расширений C/C++ для CPython |
1991 | Стандартная библиотека |
PyBind11 (рекомендуется для C++) | 2015 | |
Cython (рекомендуется для C) | 2007 | gevent, kivy |
HPy | 2019 | |
mypyc | 2017 | |
ctypes | 2003 | oscrypto |
cffi | 2013 | cryptography, pypy |
SWIG | 1996 | crfsuite |
Boost.Python | 2002 | |
cppyy | 2017 |
См. также
Полный пример из этого руководства см. на GitHub по ссылке python-samples-vs-cpp-extension.