如何从 Python 调用C++

一、说明

        Python是一种高级编程语言,它可以调用其他语言编写的函数。在 Python 中调用 C 函数的方法有两种:1)使用 Python 提供的 ctypes 库;2)使用 Python 提供的 Cython 库。

        注意:您可以在此存储库中下载此示例的完整代码。如果您对本文有任何意见,可以在那里开始一个问题,或与我联系。

二、PyBind11 vs ctypes

        基本上有两种方法可以从 Python 调用C++:使用 PyBind11 C++ 库生成 Python 模块,或者使用 cytpes Python 包访问编译的共享库。使用 PyBind11,我们可以更轻松地共享许多数据类型,而使用 ctypes 是一种低级的 C 样式解决方案

        就我而言,我希望能够利用C++性能和可移植性,但我不想放弃解释语言的交互性以进行快速探索和调试

        幸运的是,从Python调用C++并不像一开始看起来那么困难。这样,我们可以在开发C++代码的同时掌握 Python 的一些交互性。

就我而言,我想使用 Python 来:

  • 将一些问题参数传递给C++
  • 调用C++代码以运行计算密集型例程
  • 检索最终结果,以及一些用于调试的中间计算。
  • 以交互方式浏览结果,并生成绘图和报告。

        使用 ctypes 的问题在于,共享许多数据类型需要相当多的低级解决方法。例如,虽然ctypes不支持复数等基本的东西,但PyBind11使Numpy与Eigen完全互操作,需要最少的代码。

        但是,我也发现了 PyBind11 的小问题。事实证明,在重新编译C++代码并尝试重新加载 PyBind 生成的 Python 模块后,什么也没发生。重新加载编译模块的唯一方法是重新启动我的 Python 会话。无论如何,这没什么大不了的,因为Python的启动时间可以忽略不计。而且,此步骤可能在 IDE 级别自动执行。

        因此,现在的问题是如何充分利用 PyBind11。

三、与 PyBind11 共享C++类

        PyBind11 的官方文档非常出色,我可以毫无问题地开始使用它。但是,我想分享这个库的超级快速入门指南,以及我打算如何使用它。

        Pybind11 是一个仅标头库,你可以通过以下方式获取它:

 
pip install pybind11

        虽然没有必要将所有C++代码构建为一个类,但如果你有一个类要在C++和 Python 之间共享,Pybind11 会让你的事情变得非常容易。(其实我更像是那种人,总是想介绍给定项目中最少的类数)vectorstruct

        然而,在这种情况下,我发现使用外观设计模式(参见wiki)可以同时导致非常简单的Python/C++互操作性和一个不错的API。

        所以,我想出了一个简单的课程。它基本上包含:

  • 读取问题参数的构造函数。
  • 执行计算的函数。run()
  • 一些数组作为公共变量来存储结果。Eigen

        这是我的最小示例:

// mylib.h
#include 
#include 

using Eigen::Matrix, Eigen::Dynamic;
typedef Matrix, Eigen::Dynamic, Eigen::Dynamic> myMatrix;

class MyClass {

    int N;
    double a;
    double b;

public:

    Eigen::VectorXd v_data;
    Eigen::VectorXd v_gamma;

    MyClass(){}
    MyClass( double a_in, double b_in, int N_in) 
    {
        N = N_in;
        a = a_in;
        b = b_in;
    }

    void run() 
    { 
        v_data = Eigen::VectorXd::LinSpaced(N, a, b); 

        auto gammafunc = [](double it) { return std::tgamma(it); };
        v_gamma = v_data.unaryExpr(gammafunc);
    }
};

        要共享这个类,我们需要添加一些C++代码。我宁愿在一个单独的文件中执行此操作,其中包含创建 python 包装器所需的一切。

 
// pywrap.cpp
#include 
#include 
#include "mylib.h"

namespace py = pybind11;
constexpr auto byref = py::return_value_policy::reference_internal;

PYBIND11_MODULE(MyLib, m) {
    m.doc() = "optional module docstring";

    py::class_(m, "MyClass")
    .def(py::init())  
    .def("run", &MyClass::run, py::call_guard())
    .def_readonly("v_data", &MyClass::v_data, byref)
    .def_readonly("v_gamma", &MyClass::v_gamma, byref)
    ;
}

        需要强调的几点:

  • 类构造函数签名指定为.def(py::init())
  • 对于函数,我们希望释放 GIL(全局解释器锁),这将阻止我们的函数使用多个线程。run()

        最后,可以使用以下文件进行编译:CMakeLists.txt

 
cmake_minimum_required(VERSION 3.10)

project(MyLib)
set(CMAKE_CXX_STANDARD 20)
set(PYBIND11_PYTHON_VERSION 3.6)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC")

find_package(pybind11 REQUIRED)
find_package(Eigen3 REQUIRED)

pybind11_add_module(${PROJECT_NAME} pywrap.cpp)

target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO})
target_include_directories(${PROJECT_NAME} PRIVATE ${PYBIND11_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen)

        现在你已经准备好了。如果您使用的是 VS Code,则在配置 CMake 扩展后,只需按 F7 即可编译C++库。

四、从 Python 调用C++库

        这个过程非常简单,应该开箱即用。但是,有几个步骤可以优化交互式工作流,这些步骤稍微棘手一些,也值得实施。

        例如,如果您正在执行 Python 环境并且您的编译库进入一个目录,您可以执行以下操作:build

 
import sys
sys.path.append("build/")
from MyLib import MyClass

import matplotlib.pyplot as plt

Simulation = MyClass(-4,4,1000)
Simulation.run()

plt.plot(Simulation.v_data, Simulation.v_gamma, \
"--", linewidth = 3, color=(1,0,0,0.6),label="Function Value")
plt.ylim(-10,10)
plt.xlabel("x")
plt.ylabel("($f(x) = \gamma(x)$)")
plt.title("(Gamma Function: $\gamma(z) = \int_0^\infty x^{z-1} e^{-x} dx$)",fontsize = 18);
plt.show()

如何从 Python 调用C++_第1张图片

        请注意,特征向量会自动转换为 Python 数组

        Ater 修改 ,每个我们要公开的新函数或变量只需要添加一行代码。myLib.hpppywrap.cpp

        不幸的是,这不会带来完全交互式的工作流程。当您在更改后重新编译C++代码时,Python 端不会发生任何事情。即使您尝试使用以下方法重新加载 Python 模块:importtools

 
import importlib
importlib.reload(MyLib)

        什么也没发生。原因是编译后的代码无法在 Python 中重新加载

        因此,在使用 PyBind11 时,每次重新编译C++代码时都需要重新启动 Python 会话,我觉得这对于开发目的来说有点烦人。但是,这是一个很小的代价,因为Python的启动时间可以忽略不计,并且可能有一种方法可以使用一些IDE热键或其他工具使该过程自动化。

五、总结

        因此,这就是您可以轻松地从 Python 调用C++库的方式。

        特别是,这个两步过程可以产生一个非常互动的开发工作流程。尽管我们有一个编辑-编译-运行工作流,但我们在最后添加了一个解释器,所以现在我们的工作流看起来像编辑-编译-运行-探索。

        将来,我计划将两个功能合并到此工作流中:

  • 第一个是C++20模块,它应该加快大型C++项目的编译时间。不幸的是,CMake 仍然与模块不兼容(有关更新,请参阅此问题),显然人们必须依靠像 Ninja-Build 这样的构建系统才能立即提供此功能。
  • 另一件事是解决在重新编译C++代码后(手动)重新启动 Python 会话的需要。为此,我希望也许可以在VSCode级别对此做点什么。到目前为止,VS Code 中的最佳选项似乎是终止 Python 会话,然后使用 执行 Python 代码,如果尚未打开会话,则会创建一个新会话。Shift+Enter

你可能感兴趣的:(BOOST,C++,python技能小结,python,c++,开发语言)