pybind11: как упаковать код c++ и python в один пакет?
Я пытаюсь упаковать вместе существующий код Python и новый код C++ 11, используя CMake и pybind 11. Я думаю, что мне не хватает чего-то простого для добавления в Скрипты CMake, но не могу найти его нигде: примеры pybind11 имеют только код C++ и ни одного Python, другие онлайн-ресурсы довольно запутаны и не актуальны-поэтому я просто не могу понять, как упаковать функции на обоих языках вместе и сделать их доступными через Python import my_package вниз по строке... в качестве примера, я клонировал cmake_example из pybind11 и добавили функцию mult в cmake_example/mult.py
def mult(a, b):
return a * b
Как бы я сделал его видимым вместе с add и subtract, чтобы пройти тест ниже?
import cmake_example as m
assert m.__version__ == '0.0.1'
assert m.add(1, 2) == 3
assert m.subtract(1, 2) == -1
assert m.mult(2, 2) == 4
В настоящее время этот тест не проходит..
Спасибо!
2 ответов:
Самое простое решение не имеет ничего общего с pybind11 как таковым. Что обычно делают авторы, когда хотят объединить чистый Python и C / Cython / другие нативные расширения в одном пакете, так это следующее.
Вы создаете два модуля.
mymodule- это открытый интерфейс, чистый модуль Python_mymodule- это частная реализация, соответствующий модульЗатем в
mymoduleвы импортируете необходимые символы из_mymoudle(и возвращаетесь к pure Версия Python, если это необходимо).Вот пример изyarl package:
try: from ._quoting import _quote, _unquote quote = _quote unquote = _unquote except ImportError: # pragma: no cover quote = _py_quote unquote = _py_unquoteОбновление
Здесь следует сценарий. Ради воспроизводимости я делаю это против оригиналаcmake_example .
Теперь создайте чистые модули Python (внутриgit clone --recursive https://github.com/pybind/cmake_example.git # at the time of writing https://github.com/pybind/cmake_example/commit/8818f493 cd cmake_examplecmake_example/cmake_example).
cmake_example/__init__.py"""Root module of your package"""
cmake_example/math.pydef mul(a, b): """Pure Python-only function""" return a * b def add(a, b): """Fallback function""" return a + b try: from ._math import add except ImportError: passТеперь Давайте изменим существующие файлы, чтобы превратить модуль
cmake_exampleвcmake_example._math.
src/main.cpp(subtractснято для краткости)#include <pybind11/pybind11.h> int add(int i, int j) { return i + j; } namespace py = pybind11; PYBIND11_MODULE(_math, m) { m.doc() = R"pbdoc( Pybind11 example plugin ----------------------- .. currentmodule:: _math .. autosummary:: :toctree: _generate add )pbdoc"; m.def("add", &add, R"pbdoc( Add two numbers Some other explanation about the add function. )pbdoc"); #ifdef VERSION_INFO m.attr("__version__") = VERSION_INFO; #else m.attr("__version__") = "dev"; #endif }
CMakeLists.txtcmake_minimum_required(VERSION 2.8.12) project(cmake_example) add_subdirectory(pybind11) pybind11_add_module(_math src/main.cpp)
setup.pyТеперь мы можем его построить.# the above stays intact from subprocess import CalledProcessError kwargs = dict( name='cmake_example', version='0.0.1', author='Dean Moldovan', author_email='[email protected]', description='A test project using pybind11 and CMake', long_description='', ext_modules=[CMakeExtension('cmake_example._math')], cmdclass=dict(build_ext=CMakeBuild), zip_safe=False, packages=['cmake_example'] ) # likely there are more exceptions, take a look at yarl example try: setup(**kwargs) except CalledProcessError: print('Failed to build extension!') del kwargs['ext_modules'] setup(**kwargs)python setup.py bdist_wheelВ моем случае он производит
dist/cmake_example-0.0.1-cp27-cp27mu-linux_x86_64.whl(если компиляция C++ не удается, этоcmake_example-0.0.1-py2-none-any.whl). Вот каково его содержание (unzip -l ...):Archive: cmake_example-0.0.1-cp27-cp27mu-linux_x86_64.whl Length Date Time Name --------- ---------- ----- ---- 0 2017-12-05 21:42 cmake_example/__init__.py 81088 2017-12-05 21:43 cmake_example/_math.so 223 2017-12-05 21:46 cmake_example/math.py 10 2017-12-05 21:48 cmake_example-0.0.1.dist-info/DESCRIPTION.rst 343 2017-12-05 21:48 cmake_example-0.0.1.dist-info/metadata.json 14 2017-12-05 21:48 cmake_example-0.0.1.dist-info/top_level.txt 105 2017-12-05 21:48 cmake_example-0.0.1.dist-info/WHEEL 226 2017-12-05 21:48 cmake_example-0.0.1.dist-info/METADATA 766 2017-12-05 21:48 cmake_example-0.0.1.dist-info/RECORD --------- ------- 82775 9 files
После того, как вы склонировали репозиторий, CD на верхнем уровне каталога `cmake_example'
Изменение ./ src / main.cpp для включения функции "mult":
#include <pybind11/pybind11.h> int add(int i, int j) { return i + j; } int mult(int i, int j) { return i * j; } namespace py = pybind11; PYBIND11_MODULE(cmake_example, m) { m.doc() = R"pbdoc( Pybind11 example plugin ----------------------- .. currentmodule:: cmake_example .. autosummary:: :toctree: _generate add subtract mult )pbdoc"; m.def("add", &add, R"pbdoc( Add two numbers Some other explanation about the add function. )pbdoc"); m.def("mult", &mult, R"pbdoc( Multiply two numbers Some other explanation about the mult function. )pbdoc");(остальная часть файла такая же)
Теперь сделайте это:
$ cmake -H. -Bbuild $ cmake --build build -- -j3Модуль для импорта будет создан в ./каталог сборки. Перейдите к нему, а затем в оболочке python ваш пример должен работать.
Для импорта пространства имен вы можете сделать что-то с помощью
pkgutil:Создайте каталог структура:
./my_mod __init__.py cmake_example.***.soИ другая параллельная структура
./extensions /my_mod __init__.py cmake_example_py.pyИ место в
./my_mod/__init__.pyimport pkgutil __path__ = pkgutil.extend_path(__path__, __name__) from .cmake_example import add, subtract from .cmake_example_py import multВ
./extensions/my_mod/__init__.pyfrom cmake_example_py import multЗатем добавьте оба ./my_mod и ./extensions/my_mod для вашего $PYTHONPATH, это просто может работать (это делает в моем примере)
Comments