pybind11:如何将c ++和python代码打包成一个包?

时间:2017-12-01 18:11:45

标签: python c++ cmake pybind11

我正在尝试使用CMake和pybind 11将现有的Python代码和新的C ++ 11代码打包在一起。我想我遗漏了一些简单的东西添加到CMake脚本中,但无法在任何地方找到它:pybind11示例只有C ++代码而不是Python,其他在线资源相当复杂而且不是最新的 - 所以我无法弄清楚如何将两种语言的函数打包在一起并通过Python import my_package向下提供它们这一行...作为一个例子,我已经将pybind11和added a mult function中的cmake_example克隆到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

目前,此测试fails..

谢谢!

2 个答案:

答案 0 :(得分:5)

最简单的解决方案与 pybind11 无关。当作者想要在同一个包中组合纯Python和C / Cython /其他原生扩展时,通常会做什么,如下所示。

您创建了两个模块。

  1. mymodule是一个公共接口,一个纯Python模块
  2. _mymodule是一个私有实现,一个已编译的模块
  3. 然后在mymodule中从_mymoudle导入必要的符号(如果需要,还可以回退到纯Python版本)。

    以下是yarl包中的示例:

    1. quoting.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

    3. 更新

      以下是脚本。为了重现性,我正在对原始的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.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.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
      

答案 1 :(得分:2)

一旦您克隆了回购,请转到顶级目录`cmake_example&#39;

更改./src/main.cpp以包含&#34; mult&#34;功能:

#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 shell中你的例子应该工作。

对于名称空间导入,您可以使用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,它可能会起作用(在我的示例中也是如此)