PYBIND11:如何将C 和Python代码包装到一个包装中

pybind11: how to package c++ and python code into a single package?

本文关键字:包装 一个 代码 Python PYBIND11      更新时间:2023-10-16

我正在尝试使用CMAKE和PYBIND 11将现有的Python代码和新的C 11代码包装在一起。在任何地方找到它:pybind11示例只有C 代码,没有python,其他在线资源相当令人费解,而不是最新的 - 因此,我只是不知道如何将两种语言包装在一起并使它们可用通过Python的import my_package沿线...例如,我从pybind11克隆了CMAKE_EXAMPE,并在cmake_example/mult.py

中添加了一个多函数
def mult(a, b):
    return a * b

如何使其与addsubtract一起通过以下测试?

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

目前,此测试失败..

谢谢!

最简单的解决方案与 pybind11 这样无关。作者通常要在同一包装中组合纯Python和C/Cython/其他本机扩展时,通常会做什么。

您创建两个模块。

  1. mymodule是一个公共接口,一个纯Python模块
  2. _mymodule是一个私人实现,一个符合的模块

然后,在mymodule中,您会从_mymoudle导入必要的符号(必要时退回到纯Python版本)。

此示例来自YARL软件包:

  1. 引用.py

    try:
        from ._quoting import _quote, _unquote
        quote = _quote
        unquote = _unquote
    except ImportError:  # pragma: no cover
        quote = _py_quote
        unquote = _py_unquote
    
  2. _quoting.pyx

更新

这里遵循脚本。为了重现性,我正在使用原始cmake_example进行操作。

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_example

现在创建纯Python模块(内部cmake_example/cmake_example)。

cmake_example/__init__.py

"""Root module of your package"""

cmake_example/math.py

def 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.cppsubtract为简洁删除)

#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.txt

cmake_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='dean0x7d@gmail.com',
    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以包括一个"多"函数:

#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

将在./build目录中创建导入的模块。转到它,然后在Python外壳中您的示例应该起作用。

对于命名空间导入,您可以使用pkgutil进行操作:

创建目录结构:

./my_mod
    __init__.py
    cmake_example.***.so

和另一个并行结构

./extensions
    /my_mod
        __init__.py
        cmake_example_py.py

并放在./my_mod/__init__.py

import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
from .cmake_example import add, subtract
from .cmake_example_py import mult

./extensions/my_mod/__init__.py

from cmake_example_py import mult

然后,同时将./my_mod和./extensions/my_mod同时添加到您的$ PythonPath,它可能会起作用(在我的示例中确实如此)