C++和Python混合编程之Pybind11的简单使用

2024-06-01 1312阅读

C++和Python混合编程之Pybind11的简单使用

一、简介

Pybind11是C++/Python混合编程的利器之一,是一个轻量级的只包含头文件的库,用于 Python 和 C++ 之间接口转换,可以为现有的 C++ 代码创建 Python 接口绑定。Pybind11 名字里的“11”表示它完全基于现代 C++ 开发(C++11 以上),所以没有兼容旧系统的负担。它使用了大量的现代 C++ 特性,不仅代码干净整齐,运行效率也更高。


二、平台环境

1、系统:Windows10

2、Python虚拟环境工具:Anaconda3

3、C++ IDE:Visual Studio 2022

4、Python版本:3.7.16


三、C++/Python相互调用的方法

简单介绍如何实现两种语言之间相互调用

1、Python调用C++代码: 通过调用动态库的方式完成,将C++代码编译生成动态库文件(Win下为.DLL),Python调用的话需要将库后缀改为(.pyd),然后将动态库拷贝到Python文件主目录,代码内导入库模块即可;

2、C++代码调用Python: 主要通过调用Python代码解释器来实现。


四、代码实践

用代码实例简单展现Pybind11的功能

1、基础环境搭建

1.1、安装Pybind11库

有多种安装方式,这里通过pip命令来安装,如果使用了虚拟环境,安装前记得激活相应的虚拟环境:

安装命令如下:

(py37) C:\Users\xxx> pip install pybind11

1.2、Visual Studio项目属性配置:

具体路径根据自己项目实际情况而定

1). 通用编译属性设置:

  • 属性–>常规–>常规属性–>配置类型:动态库(.dll);
  • 属性–>高级–>高级属性–>目标文件扩展名:.pyd;

    2). C/C++附加包含目录include:

    • 属性–>C/C++ -->常规–>附加包含目录:
    • D:\Anaconda3\envs\py37\include
    • D:\Anaconda3\envs\py37\Lib\site-packages\pybind11\include

      3). 链接器附加库目录和库文件:

      • 属性–>链接器–>常规–>附加库目录:D:\Anaconda3\envs\py37\libs
      • 属性–>链接器–>输入–>附加依赖项:python3.lib,python37.lib

        具体操作如下:

        C++和Python混合编程之Pybind11的简单使用

        C++和Python混合编程之Pybind11的简单使用

        C++和Python混合编程之Pybind11的简单使用

        C++和Python混合编程之Pybind11的简单使用

        C++和Python混合编程之Pybind11的简单使用


        1.3、系统环境变量设置

        1)、因为在C++调用Python代码过程中遇到错误,经过查资料找到了解决办法(stackoverflow讨论地址),以下环境具体路径根据自己项目实际情况而定。

        2)、要在C++中调用Python解释器(py::scoped_interpreter guard{};),需要添加两个系统环境变量,以便Pybind11能够找到解释器位置:

        • PYTHONHOME:D:\Anaconda3\envs\py37
        • PYTHONPATH:D:\Anaconda3\envs\py37\Lib;D:\Anaconda3\envs\py37\Lib\site-packages;D:\Anaconda3\envs\py37\DLLs

          3)、如果不设置这两个环境变量会出现以下错误:

          • Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding

            Python runtime state: core initialized

            ModuleNotFoundError: No module named ‘encodings’

            4)、设置完后重启电脑生效

            C++和Python混合编程之Pybind11的简单使用

            从打印的错误可以看出[PYTHONHOME,PYTHONPATH]两个系统环境变量未设置。

            设置系统变量:

            C++和Python混合编程之Pybind11的简单使用

            C++和Python混合编程之Pybind11的简单使用

            注意: 增加这两个环境变量后可能导致Anaconda3虚拟环境命令找不到,进而无法激活虚拟环境,如果出现则删除这两个环境变量即可(暂时没找到好的解决办法),删除后重启电脑。


            2、Python使用C++代码动态库

            演示两个流程:

            • C++编译动态库;
            • Python代码中调用动态库。
              2.1、C++编译动态库

              演示C++编译动态库以供Python调用

              代码示例:

              #include 
              #include 
              #include 
              #include 
              #include 
              #include 
              #include   // 转换标准容器必须的头文件
              namespace py = pybind11;  // 名字空间别名,简化代码
              class Point final
              {
              private:
              	int x = 0;
              public:
              	Point() = default;
              	~Point() = default;
              	Point(int a) : x(a) {}
              public:
              	int get() const
              	{
              		return x;
              	}
              	void set(int a)
              	{
              		x = a;
              	}
              };
              // 用lambda表达式来测试
              PYBIND11_MODULE(pydemo, m)  // 定义Python模块pydemo
              {
              	m.doc() = "pybind11 demo doc";
              	
              	m.def("info",
              		[]()
              		{
              			py::print("c++ version: ", __cplusplus);
              		}
              	);
              	m.def("add",
              		[](int a, int b)
              		{
              			return a + b;
              		}
              	);
              	m.def("use_str",
              		[](const std::string& str)  // 定义python函数,入参是string
              		{
              			py::print(str);
              			return str + "!!";  // 返回string
              		}
              	);
              	m.def("use_tuple",
              		[](std::tuple x)  // 定义python函数,入参是tuple
              		{
              			std::get(x)++;
              			std::get(x)++;
              			std::get(x) += "??";
              			return x;
              		}
              	);
              	m.def("use_list",
              		[](std::vector& v)  // 定义python函数,入参是vector
              		{
              			auto vv = v;
              			py::print("input :", vv);
              			vv.push_back(100);
              			vv.push_back(200);
              			return vv;
              		}
              	);
              	m.def("use_map",
              		[](std::map& m)  // 定义python函数,入参是map
              		{
              			auto mm = m;
              			py::print("input : ", mm);
              			mm["name"] = "LiMing";
              			mm["gender"] = "male";
              			return mm;
              		}
              	);
              	// C++ 里的类也能够等价地转换到 Python 里面调用,这要用到一个特别的模板类 class_
              	py::class_(m, "Point")  // 定义Python类
              		.def(py::init())           // 导出构造函数
              		.def(py::init())      // 导出构造函数
              		.def("get", &Point::get)   // 导出成员函数
              		.def("set", &Point::set)   // 导出成员函数
              		;
              }
              #if 0
              // 用普通函数来测试
              void info()
              {
              	std::cout 
              	return a + b;
              }
              PYBIND11_MODULE(pydemo, m)
              {
              	m.doc() = "pybind11 demodoc";
              	m.def("info", &info, "cpp info");
              	m.def("add", &add, "add func");
              }
              #endif
              #if 0
              int main()
              {
              	return 0;
              }
              #endif
              
              pem编译输出结果如下:/em/p pre class="brush:python;toolbar:false"生成开始于 13:12... 1------ 已启动生成: 项目: PythonAndCPP, 配置: Release x64 ------ 1main.cpp 1> 正在创建库 F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.lib 和对象 F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.exp 1>正在生成代码 1>441 of 3091 functions (14.3%) were compiled, the rest were copied from previous compilation. 1> 186 functions were new in current compilation 1> 391 functions had inline decision re-evaluated but remain unchanged 1>已完成代码的生成 1>PythonAndCPP.vcxproj -> F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.pyd ========== 生成: 1 成功,0 失败,0 最新,0 已跳过 ========== ========== 生成 于 13:12 完成,耗时 03.362 秒 ==========

              C++和Python混合编程之Pybind11的简单使用


              2.2、在Python中调用动态库

              首先需要将动态库拷贝到Python项目主目录下,然后才能在python代码中导入模块使用

              首先需要将动态库拷贝到Python项目主目录下,如下图所示:

              C++和Python混合编程之Pybind11的简单使用

              代码示例:

              import pydemo  # 导入模块,与C++代码中定义的模块名一致
              def test():
                  pydemo.info()
              	
              	// 调用add函数
                  print("Test add func: ")
                  print(pydemo.add(1,2))
              	
              	// 调用Point类极其成员函数
                  print("Test Point class: ")
                  p = pydemo.Point(10)
                  print(p.get())
                  p.set(88)
                  print(p.get())
              	
              	// 字符串转换测试:std::string->str
                  print("Test str: ")
                  print(pydemo.use_str("hello"))
              	
              	// 元组转换测试:std::tuple->tuple
                  print("Test tuple: ")
                  t = (11,22,"No")
                  print(pydemo.use_tuple(t))
              	
              	// 列表转换测试:std::vector->list
                  print("Test list: ")
                  l = []
                  print(pydemo.use_list(l))
              	
              	// 键值对转换测试:std::map->map
                  print("Test map: ")
                  m = {}
                  print(pydemo.use_map(m))
                  
              def main():
                  test()
              if __name__ =="__main__":
                  main()
                  
              

              运行结果如下:

              (py37) F:\code\pydemo\Test>python main.py
              c++ version:  199711
              Test add func: 
              3
              Test Point class:
              10
              88
              Test str:
              hello
              hello!!
              Test tuple:
              (12, 23, 'No??')
              Test list:
              input : []
              [100, 200]
              Test map:
              input :  {}
              {'gender': 'male', 'name': 'LiMing'}
              

              3、C++调用Python解释器

              用两种方式演示如何在C++代码中调用Python代码解释器:

              • 直接运行python代码
              • 导入python外部模块执行代码,更灵活

                注意细节:

                • 需要将python代码文件拷贝至C++项目主目录下;
                • 将所用python版本的python3.dll,python37.dll两个动态库拷贝至C++可执行文件(.exe)所在目录,否则可能无法运行或运行出错。
                  3.1 拷贝代码运行所需文件

                  1)、拷贝python代码文件

                  C++和Python混合编程之Pybind11的简单使用

                  2)、拷贝DLL文件

                  C++和Python混合编程之Pybind11的简单使用


                  3.2、代码演示

                  代码示例:

                  python代码文件:pydemo.py

                  python代码:

                  import os
                  from typing import List, AnyStr
                  def get_files(path:str) -> List[AnyStr]:
                      """
                      遍历目录下所有文件并返回结果
                      :param path: 目录
                      :return: 返回文件列表
                      """
                      if not os.path.exists(path):
                          return []
                      
                      # 递归遍历文件夹下的所有文件
                      files = []
                      for (dirpath, dirnames, filenames) in os.walk(path):
                          files += filenames
                      return files
                  if __name__ == "__main__":
                      pass
                  

                  CPP代码:

                  #include 
                  #include 
                  #include 
                  #include 
                  #include 
                  #include   // 要用解释器需要包含此头文件
                  namespace py = pybind11;  // 名字空间别名,简化代码
                  #if 1
                  void test_pybind11()
                  {
                  	py::scoped_interpreter guard{};  // 初始化Python解释器
                  	// 1、直接运行python代码
                  	std::cout 
                  		// 使用原始字符串R"()"
                  		py::exec(R"(
                  def pow(a,n):
                  	return a**n)");
                  		auto func = py::module::import("__main__").attr("pow");
                  		auto res = func(2, 3).cast
                  		std::cout 
                  		auto module = py::module::import("pydemo");  // 导入python外部模块pydemo,python中一个.py文件就是一个模块
                  		auto res = module.attr("get_files")("C:\\Users\\xxx\\Pictures\\wallpaper");
                  		std::cout 
                  			std::cout 
                  		std::cout 
                  	test_pybind11();
                  	return 0;
                  }
                  #endif
                  
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]