如何在C++ 中调用 Python

在一些场景下,C++ 通过调用脚本语言实现一些功能会比用C++实现更加的方便。

这里要介绍的是pybind11,pybind11 借鉴了“前辈”Boost.Python,能够在 C++ 和 Python 之间自由转换,任意翻译两者的语言要素,比如把 C++ 的 vector 转换为 Python 的列表,把 Python 的元组转换为 C++ 的 tuple,既可以在 C++ 里调用 Python 脚本,也可以在 Python 里调用 C++ 的函数、类。

pybind11 名字里的“11”表示它完全基于现代 C++ 开发(C++11 以上),所以没有兼容旧系统的负担。它使用了大量的现代 C++ 特性,不仅代码干净整齐,运行效率也更高。

虽然 pybind11主要致力于使用 C + + 扩展 Python,但也可以反其道而行之: 将 Python 解释器嵌入到 C + + 程序中。

Python里调用c++的文章很多,这篇文章主要聚焦于如何在C++中调用Python。

前提

既然是和Python进行交互,那么肯定需要安装Python的,Linux系统下一般自带。

在C++中调用Python

只需几行 CMake 和 pybind11: :embed 就可以创建带有嵌入式解释器的基本可执行文件。

官方的CMake示例:

cmake_minimum_required(VERSION 3.4)
project(example)

find_package(pybind11 REQUIRED)  # or `add_subdirectory(pybind11)`

add_executable(example main.cpp)
target_link_libraries(example PRIVATE pybind11::embed)

这里,我使用的是CMake的FetchContent更加方便

cmake_minimum_required(VERSION 3.20)
project(CppBindPythonDemo)

set(CMAKE_CXX_STANDARD 17)

include(FetchContent)
FetchContent_Declare(
        pybind11
        GIT_REPOSITORY https://github.com/pybind/pybind11.git
)

FetchContent_MakeAvailable(pybind11)

add_executable(CppBindPythonDemo main.cpp)
target_link_libraries(CppBindPythonDemo PRIVATE pybind11::embed)

执行Python代码

运行 Python 代码有几种不同的方法。一种选择是使用 evalexeceval _ file

hello_world:

//main.cpp
#include <pybind11/embed.h> // everything needed for embedding
namespace py = pybind11;

int main() {
    
    
    py::scoped_interpreter guard{
    
    }; // start the interpreter and keep it alive

    py::print("Hello, World!"); // use the Python API
}

示例1:

#include <pybind11/embed.h>
namespace py = pybind11;

int main() {
    py::scoped_interpreter guard{};

    py::exec(R"(
        kwargs = dict(name="World", number=42)
        message = "Hello, {name}! The answer is {number}".format(**kwargs)
        print(message)
    )");
}

示例2:

#include <pybind11/embed.h>
namespace py = pybind11;
using namespace py::literals;

int main() {
    py::scoped_interpreter guard{};

    auto kwargs = py::dict("name"_a="World", "number"_a=42);
    auto message = "Hello, {name}! The answer is {number}"_s.format(**kwargs);
    py::print(message);
}

示例3:

#include <pybind11/embed.h>
#include <iostream>

namespace py = pybind11;
using namespace py::literals;

int main() {
    py::scoped_interpreter guard{};

    auto locals = py::dict("name"_a="World", "number"_a=42);
    py::exec(R"(
        message = "Hello, {name}! The answer is {number}".format(**locals())
    )", py::globals(), locals);

    auto message = locals["message"].cast<std::string>();
    std::cout << message;
}

可以看到,有一个共同点,在执行Python代码之前,都会先创建py::scoped_interpreter guard{};

在使用任何 Python API (包括 pybind11中的所有函数和类)之前,必须对解释器进行初始化。 scoped_interpreter RAII 类负责解释器的生命周期。scoped_interpreter生命周期结束后,解释器关闭并清理内存,在此之后就不能调用 Python 函数了。

导入模块

除了上述的执行方式外,Python模块也可以导入,系统模块或者是自己写的都可以。

调用系统模块:

py::module_ sys = py::module_::import("sys");
py::print(sys.attr("path"));

py::module math = py::module::import("math");
py::object result = math.attr("sqrt")(25);
std::cout << "Sqrt of 25 is: " << result.cast<float>() << std::endl;

调用自己编写的模块(即py文件)

这里我写了一个py_bind_demo.py文件,里面有一个函数fib和一个类PyClass

#py_bind_demo.py
def fib(n):
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()


class PyClass:
    __name = "py_module"

    def print_name(self):
        print("call python print_name func")
        print(self.__name)

    def fib(self, n):
        """Print a Fibonacci series up to n."""
        a, b = 0, 1
        while a < n:
            print(a, end=' ')
            a, b = b, a + b
        print()

接下来,在main.cpp中调用

//用py::object导入python类模块并进行类的初始化
py::object PyClass = py::module::import("py_bind_demo").attr("PyClass");
//创建类对象
py::object pyClass = PyClass();
//调用类成员函数
pyClass.attr("print_name")();
pyClass.attr("fib")(4);

编译运行,结果报错

terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  ModuleNotFoundError: No module named 'py_bind_demo'

提示找不到此模块,为什么呢?

经过分析后发现,是因为目录结构的问题,我的main.cpppy_bind_demo.py在同一路径下,而不是py文件在可执行文件路径下,故运行可执行文件时,提示找不到模块。

所以,当调用Python时,需要将py文件放入可执行文件路径下

如图:

之前 之后
在这里插入图片描述 在这里插入图片描述

这下就可以成功运行了
在这里插入图片描述

总结

本文简单介绍了如何在C++中调用Python的方法,还有很多细节并未展开,可自行查询相关文档。

pybind11: Seamless operability between C++11 and Python (github.com)

pybind11 documentation

猜你喜欢

转载自blog.csdn.net/no_say_you_know/article/details/128593337