定制Pythong虚拟机之Python类型在C++中的创建和使用

一、目录

一、目录二、介绍三、编译 Python 源码四、运行时所需的库及文件、CMakeLists.txt 文件五、相关 API,及Python的类型在C++中的创建及使用5.1 初始化和清理 Python5.2 C++中创建和转换 Python的基本类型5.2.1 整型5.2.2 浮点型5.2.3 字符串5.2.4 列表5.2.5 元组5.2.6 字典六、完整实例程序

二、介绍

本推送主要是我自己整理的 夏曹俊 老师的课程笔记,这里只写 如何在 在 C++ 中调用 Python 脚本,至于用C++给Python做拓展库的部分就不说了,有兴趣的可以在51cto上购买该老师的视频课程。

C++调用Python,也就是定制 Python 虚拟机,然后嵌入到我们C++开发的项目中。

平时开发项目时,可能会需要一些配置文件来做配置项。那么这时候你就可以用 Python 脚本来做配置项,然后在 C++ 中调用 Python 脚本来获取配置项。感觉这样做确实方便,我之前配置项都是用 XML 文件来做的,然后根据 XML 文件的结构自己写一些函数去解析。

另外,Python中的库实在是太丰富了,在不涉及性能瓶颈的地方,完全可以去调用 Python 脚本来实现。

本篇推送先介绍一下 Python 中的一些类型在C++中的表示及使用,在以后的推送中再介绍 在C++中调用Python的函数、类、模块。

本篇介绍的 在C++中的Python类型 有:整形、浮点型、字符串、列表、元组、字典。还有像 集合(set)这样的类型就没写了,用法基本都一样。

但是在介绍这些基本类型之前,得先说点其他内容:Python源码的编译、需要的依赖、等。

三、编译 Python 源码

这部分在之前的这篇推送中写过:在Windows上用 VS2017编译,编译安装好后再添加一下注册表,具体操作看一下:Python源码在Win下和Linux下的编译,C++调用Python库绘制等高线 这篇推送,这里不重复了。

最好还是编译一下源码,不要直接从网上下载Python安装程序,用安装目录下的那些库可能会出问题(我没试)。

四、运行时所需的库及文件、CMakeLists.txt 文件

为了避免引入多余的Python库,建议创建Python虚拟环境,然后在这个虚拟环境下写Python脚本。最后把虚拟环境中的库复制到我们的C++项目中,具体复制哪些文件可以参考:Python源码在Win下和Linux下的编译,C++调用Python库绘制等高线 后半部分

Python创建虚拟环境(Win中):
1.在cmd中执行:python -m venv .venv(.venv是虚拟环境的文件夹名),会在当前目录中创建一个 .venv 文件夹名;
2.激活虚拟环境:.venv\Scripts\activate.bat
3.用CLion或者PyCharm等IDE写Python脚本时,设置一下你的Python解释为这个虚拟环境中的Python.exe

如果你用CMake编译的话,需要把Python库给链接进来,就是在 CMakeLists.txt中添加下面几行:

# 把Python的头文件的目录包括进来
include_directories("D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/Include")

# 把Python lib 目录路径 链接进来
link_directories("D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python")

## 链接 Python lib (可以不用)
#link_libraries(
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/_bz2.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/_contextvars.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/_socket.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/_tkinter.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/python37.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/select.lib"
#        "D:/PersonalAllAboutStudy/Programming/CLion_Python_Demo/demo8_3/Python/unicodedata.lib"
#)

如果你用VS的话,就设置一下项目的属性,然后把这些库给链接进去、把头文件包含进去。

具体也可以看一下我上次写的哪个推送,里面做了一个简单的C++调用Python的例子。

五、相关API,Python的类型在C++中的创建和使用

5.1 初始化和清理 Python

设置Python的 Home 路径

Py_SetPythonHome(L"./");

置Python的Home路径,一般就设置为当前程序运行的路径,这样Python解释器在查找库及模块时就在当前目录下查找

初始化 Python

Py_Initialize();

Python解释器初始化

不再调用 Python 时,清理Python

Py_Finalize();

清理Python,和 Py_Initialize是一对的,程序结束,不用Python了,要做这一步。

5.2 C++中创建和转换 Python的基本类型

首先说明一下,在C++这边,一切的Python类型都是 PyObject,一直往下看就知道了。而且 每在 C++ 中创建一个 PyObject 在不用时就需要释放!

Py_XDECREF(PyObject*);
5.2.1 整型
PyObject* PyLong_FromLong(long);  /// 创建一个 Python的 整型
bool PyLong_Check(PyObject*);     /// 判断是否是Python整型
long PyLong_AsLong(PyObject*);    /// 把Python整型转化为 C++ 整形(long)

如:

/// 1. 在 C++ 中创建 Python的整形
PyObject* a_PyLong = PyLong_FromLong(1314);
/// Python 整型检测
bool a_isPyLong = PyLong_Check(a_PyLong);  // NOLINT
/// 把 Python 的整形 转化到 C++
long a_int = PyLong_AsLong(a_PyLong);
/// 打印结果
std::cout << "***************** Long *****************" << std::endl;
std::cout << std::setw(8) << std::left << "a_int: " << std::setw(8) << std::left << a_int << std::setw(32)
            << std::setw(8) << std::left << "\tis long: " << std::boolalpha << std::setw(8) << std::left << a_isPyLong
            << std::endl;
/// 及时销毁变量
Py_XDECREF(a_PyLong);
5.2.2 浮点型
PyObject* PyFloat_FromDouble(double);  /// 创建一个 Python 的 浮点型
bool PyFloat_Check(PyObject*);         /// 判断是否是 Python 浮点型
double PyFloat_AsDouble(PyObject*);    /// 把 python 浮点型 转化为 C++ 的 double

具体使用请看下面的代码:

/// 2. 在 C++ 中创建 Python 的浮点型
PyObject* b_PyFloat = PyFloat_FromDouble(13.14);
/// Python 浮点型检测
bool b_isPyFloat = PyFloat_Check(b_PyFloat);  // NOLINT
/// 把 Python 的浮点型 转化到 C++
double b_double = PyFloat_AsDouble(b_PyFloat);
/// 打印结果
std::cout << "***************** Float *****************" << std::endl;
std::cout << std::setw(8) << std::left << "b_double: " << std::setw(8) << std::left << b_double << std::setw(32)
            << std::setw(8) << std::left << "\tis float: " << std::boolalpha << std::setw(8) << std::left << b_isPyFloat
            << std::endl;
/// 及时销毁变量
Py_XDECREF(b_PyFloat);
5.2.3 字符串

避免使用中文!

PyObject* PyUnicode_FromString(const char*);  /// 创建一个 Python 的字符串
bool PyUnicode_Check(PyObject*);              /// 判断是否是 Python 的字符串型
PyUnicode_AsWideChar(PyObject*, wchar_t*, Py_ssize_t); /// 把 Python 的字符串 转化成 C++ 的 wchar_t* 宽字符串

其中的:
1.wchar_t 是宽字符 16位,0~65535,共65536个字符;
2.Py_ssize_t__int64,你把他看成一个整形就行!

具体使用请看下面的代码:

/// 3. 在 C++ 中创建 Python 的字符串(避免用中文)
PyObject* c_PyUnicode = PyUnicode_FromString("Mitch");
/// Python 字符串型检测
bool c_isPyUnicode = PyUnicode_Check(c_PyUnicode);  // NOLINT
/// 把 Python 的字符串 转化到 C++(宽字符 16 位,wchar_t 0~65535,共65536个字符)
wchar_t str_buf[1024]{'\0'};
PyUnicode_AsWideChar(c_PyUnicode, str_buf, sizeof(str_buf));
/// 打印结果
std::cout << "***************** String(Unicode) *****************" << std::endl;
std::wcout << std::setw(8) << std::left << "str_buf: " << std::setw(8) << std::left << str_buf << std::setw(32)
            << std::setw(8) << std::left << "\tis unicode: " << std::boolalpha << std::setw(8) << std::left << c_isPyUnicode
            << std::endl;
/// 及时销毁变量
Py_XDECREF(c_PyUnicode);
5.2.4 列表
PyObject* PyList_New(Py_ssize_t);   /// 创建一个 Python 的 列表,参数指定了列表的元素个数
int PyList_Append(PyObject* list, PyObject* ele);  /// 向列表中追加元素,返回值可不管,正常都能追加进去。
bool PyList_Check(PyObject*);       /// 判断是否是 Python 中的列表
Py_ssize_t PyList_Size(PyObject*);  /// 返回 列表 的 元素个数
PyObject* PyList_GetItem(PyObject* list, Py_ssize_t index);  /// 获取 列表 对应索引处的值
int PyList_SetItem(PyObject* list, Py_ssize_t index, PyObject* ele);  /// 修改(设置) 列表 的 index索引处 的元素为 ele
int PyList_Insert(PyObject* list, Py_ssize_t index, PyObject* ele);   /// 在列表的 索引为index处插入一个元素 ele
int PyList_SetSlice(PyObject* list, Py_ssize_t start, Py_ssize_t end, nullptr);  /// 把列表的 [start, end)区间内的元素置空,也就是删掉了

其中:
1.因为Python的列表可以修改,所以可以不用指定列表的元素个数,创建时,传递0即可。
2.Python没有直接开放删除列表元素的API,就通过 PyList_SetSlice 间接实现了。

具体使用请看下面的代码:

/// 4. 在 C++ 中创建 Python 的 列表
PyObject* d_PyList = PyList_New(0);                     //  list 不用在开始时固定其大小,可以随时扩充
PyList_Append(d_PyList, PyLong_FromLong(1314));             //  追加一个整型数据到列表中
PyList_Append(d_PyList, PyFloat_FromDouble(13.14));         //  追加一个浮点型数据到列表中
PyList_Append(d_PyList, PyUnicode_FromString("Mitch"));  //  追加一个字符串型数据到列表中
/// Python 列表类型检查
bool d_isPyList = PyList_Check(d_PyList);  //  NOLINT
/// 获取列表的大小
Py_ssize_t size = PyList_Size(d_PyList);   //  Py_ssize_t 是 __int64,一个特别大的整形,一般情况当成 int 就好
/// 获取 列表的元素(这样拿到的是 PyObject类型的,需要根据类型转化一下;)
PyObject* d0_val = PyList_GetItem(d_PyList, 0);  //  获取 index=0上的元素;
int d0 = PyLong_AsLong(d0_val);
PyObject* d1_val = PyList_GetItem(d_PyList, 1);  //  获取 index=1上的元素;
double d1 = PyFloat_AsDouble(d1_val);
PyObject* d2_val = PyList_GetItem(d_PyList, 2);  //  获取 index=2上的元素;
wchar_t d2[128]{'\0'};
PyUnicode_AsWideChar(d2_val, d2, sizeof(d2));
/// 修改 列表的元素
PyList_SetItem(d_PyList, 0, PyLong_FromLong(1314520));
/// 在列表中间 插入 元素
PyList_Insert(d_PyList, 1, PyFloat_FromDouble(3.1));
/// 删除 列表中的元素(把一个区间内的元素设置为 空,就是删除了)
PyList_SetSlice(d_PyList, 0, 1, nullptr);  //  把 [0, 1) 范围内的元素置空,实际上就是删除了 index=1 上的元素。
/// 遍历列表,很简单,通过 PyList_Size 获取列表的大小,然后用循环,循环内用 PyList_GetItem 获取元素、用 PyList_SetItem 修改元素
std::cout << "***************** List(Unicode) *****************" << std::endl;
for (int i = 0; i < PyList_Size(d_PyList); ++i)
{
    PyObject* tmpObj = PyList_GetItem(d_PyList, i);
    if (PyLong_Check(tmpObj))  //  NOLINT
        std::cout << i << ": " << PyLong_AsLong(tmpObj) << std::endl;
    else if (PyFloat_Check(tmpObj))
        std::cout << i << ": " << PyFloat_AsDouble(tmpObj) << std::endl;
    else if (PyUnicode_Check(tmpObj))    //  NOLINT
    {
        wchar_t tmpBuf[1024]{'\0'};
        PyUnicode_AsWideChar(tmpObj, tmpBuf, sizeof(tmpBuf));
        std::wcout << i << L": " << tmpBuf << std::endl;
    }
    else
        std::cout << i << ": Unknown element!" << std::endl;
}
std::cout << "***************** ********** *****************" << std::endl;
/// Note,在循环时最 判断一下当前的 PyObject是哪种类型,如果当前元素又是一个列表,你可以对其在用一个循环来打印一下,如下:
PyObject* d_PyList2 = PyList_New(0);                       //  list 不用在开始时固定其大小,可以随时扩充
PyList_Append(d_PyList2, PyLong_FromLong(1314));                //  追加一个整型数据到列表中
PyList_Append(d_PyList2, PyLong_FromLong(1314520));             //  追加一个整型数据到列表中
PyList_Append(d_PyList, d_PyList2);
/// 遍历列表
for (int i = 0; i < PyList_Size(d_PyList); ++i)
{
    PyObject* tmpObj = PyList_GetItem(d_PyList, i);
    if (PyLong_Check(tmpObj))  //  NOLINT
        std::cout << i << ": " << PyLong_AsLong(tmpObj) << std::endl;
    else if (PyFloat_Check(tmpObj))
        std::cout << i << ": " << PyFloat_AsDouble(tmpObj) << std::endl;
    else if (PyUnicode_Check(tmpObj))    //  NOLINT
    {
        wchar_t tmpBuf[1024]{'\0'};
        PyUnicode_AsWideChar(tmpObj, tmpBuf, sizeof(tmpBuf));
        std::wcout << i << ": " << tmpBuf << std::endl;
    }
    else if (PyList_Check(tmpObj))  //  NOLINT
    {
        std::cout << i << ": ";
        /// 遍历子列表
        for (int j = 0; j < PyList_Size(tmpObj); ++j)
        {
            PyObject* tmpObjjj = PyList_GetItem(tmpObj, j);
            if (PyLong_Check(tmpObjjj))  //  NOLINT
                std::cout << j << ": " << PyLong_AsLong(tmpObjjj) << "\t";
            else if (PyFloat_Check(tmpObjjj))
                std::cout << j << ": " << PyFloat_AsDouble(tmpObjjj) << "\t";
            else if (PyUnicode_Check(tmpObjjj))    //  NOLINT
            {
                wchar_t tmpBuf[1024]{'\0'};
                PyUnicode_AsWideChar(tmpObjjj, tmpBuf, sizeof(tmpBuf));
                std::wcout << j << ": " << tmpBuf << "\t";
            }
            /// 及时清理
            Py_XDECREF(tmpObjjj);
        }
        std::cout << std::endl;
    }
    else
        std::cout << "Unknown element!" << std::endl;
    /// 清理
    Py_XDECREF(tmpObj);
}
/// 及时销毁变量
Py_XDECREF(d_PyList);
5.2.5 元组

看懂了上面的 列表 的操作,元组这就没什么说的了。
1.只不过就是把 API 中的List换成Tuple罢了;
2.另外,由于 Python中的元组是无法修改的,所以不能用 追加、删除的操作,那个设置元素值的API还是可以用的,用它进行初始化;
3.创建元组时,一定要设置他的元素个数,之后就不能再修改了。

/// 5. 在 C++ 中创建 Python 的元组,元素不可修改,所以在创建时就必须给定其大小!
PyObject* e_tuple = PyTuple_New(3);
/// 元组这就比 列表那个简单多了,用:
/**
    * PyTuple_SetItem()、设置元素,用法同列表
    * PyTuple_GetItem()、设获取元素,用法同列表
    * PyTuple_Size()、   获取元组的大小,用法同列表
    * PyTuple_Check()    元组类型检查,用法同列表
    * */
/// 及时清理
Py_XDECREF(e_tuple);
5.2.6 字典
PyObject* PyDict_New();   /// 创建一个字典变量
PyDict_SetItem(PyObject* dict, PyObject* key, PyObject* value);  /// 设置(修改)字典的 键 对应的 值。如果键不存在,就时新增一个键值对
bool PyDict_Check(PyObject*);    /// 判断是否是字典类型
bool PyDict_Contains(PyObject* dict, PyObject* key);  /// 判断该字典是否包含 键 key
PyObject* PyDict_GetItem(PyObject* dict, PyObject*, key);  /// 获取字典中的 键 key 对应的值!
int PyDict_DelItem(PyObject* dict, PyObject* key);    /// 删除 字典中的 键值对,最好先判断一下是否存在该键。
PyObject *keys = PyDict_Keys(f_PyDict);     /// 获取该字典中所有的 Key 放在一个 PyList 里面,PyList也是PyObject对象哦!
PyObject *values = PyDict_Values(f_PyDict); /// 获取该字典中所有的 value 放在一个 PyList 里面。

/// 从第pos个键值对开始 读取键值对,直到读完,读完了函数返回 0。一般跟 while 循环一块用,遍历键值对
int PyDict_Next(PyObject *dict, Py_ssize_t *pos, PyObject **key, PyObject **value);

具体的使用,都写在了下面代码的注释中了,包括两种遍历字点的方法:

/// 6. 在 C++ 中创建 Python 的字典,key 和 value 都是 PyObject。
PyObject* f_PyDict = PyDict_New();  //  后面通过 PyDict_SetItem 直接给字典新增键值对,也可以通过该函数修改键值对
//  设置字典的 Age 为 22
PyDict_SetItem(f_PyDict, PyUnicode_FromString("Age"), PyLong_FromLong(22));
//  设置字典的 Name 为 Mitch
PyDict_SetItem(f_PyDict, PyUnicode_FromString("Name"), PyUnicode_FromString("Mitch"));
//  设置字典的 Gender 为 male
PyDict_SetItem(f_PyDict, PyUnicode_FromString("Gender"), PyUnicode_FromString("male"));
/// Python 字典类型检查
bool d_isPyDict = PyDict_Check(f_PyDict);  // NOLINT
/// 判断 字典 是否包含某个 key
bool d_isContainWeight = PyDict_Contains(f_PyDict, PyUnicode_FromString("Weight"));
/// 获取 字典 关键字 的 值,并转化到 C++ 中相应的类型
PyObject* val_age = PyDict_GetItem(f_PyDict, PyUnicode_FromString("Age"));
PyObject* val_name = PyDict_GetItem(f_PyDict, PyUnicode_FromString("Name"));
PyObject* val_gender = PyDict_GetItem(f_PyDict, PyUnicode_FromString("Gender"));
int age_ = PyLong_AsLong(val_age);
wchar_t name[128]{'\0'};   PyUnicode_AsWideChar(val_name, name, sizeof(name));
wchar_t gender[128]{'\0'}; PyUnicode_AsWideChar(val_gender, gender, sizeof(gender));
/// 打印结果
std::cout << "***************** Dict *****************" << std::endl;
std::wcout << "Age: " << age_ << "\tName: " << name << "\tGender: " << gender << std::endl
            << "is dict: " << std::boolalpha << d_isPyDict << std::endl
            << "is contains Weight: " << std::boolalpha << d_isContainWeight
            << std::endl;
/// 删除 字典中的 键值对(最好与 PyDict_Contains 一块使用,只有存在,然后再判断)
if (PyDict_Contains(f_PyDict, PyUnicode_FromString("Gender")))
    PyDict_DelItem(f_PyDict, PyUnicode_FromString("Gender"));

/// 遍历 key 和 value 返回所有的关键字、值,的两种方法
//  法一:PyDict_Keys 返回 所有 key 的 PyList;PyDict_Values 返回 所有 value 的 PyList。
{
    PyObject *keys = PyDict_Keys(f_PyDict);     /// PyList
    PyObject *values = PyDict_Values(f_PyDict); /// PyList

    wchar_t buff[1024] = {'\0'};
    std::cout << "All keys: ";
    for (Py_ssize_t i = 0; i < PyList_Size(keys); ++i) {
        PyObject *val_ = PyList_GetItem(keys, i);
        if (PyUnicode_Check(val_))  // NOLINT
        {
            PyUnicode_AsWideChar(val_, buff, sizeof(buff));
            std::wcout << buff << "\t";
        }
        else if (PyLong_Check(val_))  // NOLINT
            std::wcout <<PyLong_AsLong(val_) << "\t";
        else
            std::cout << "Unknown" << "\t";
    }

    std::cout <<"\nAll values: ";
    for (Py_ssize_t i = 0; i < PyList_Size(values); ++i) {
        PyObject *val_ = PyList_GetItem(values, i);
        if (PyUnicode_Check(val_))  // NOLINT
        {
            PyUnicode_AsWideChar(val_, buff, sizeof(buff));
            std::wcout << buff << "\t";
        }
        else if (PyLong_Check(val_))  // NOLINT
            std::wcout <<PyLong_AsLong(val_) << "\t";
        else
            std::cout << "Unknown" << "\t";
    }

    /// 清理
    Py_XDECREF(keys);
    Py_XDECREF(values);
}
//  法二:用 PyDict_Next 配合 while 循环来做
{
    wchar_t buff[1024] = {'\0'};
    Py_ssize_t pos = 0;
    PyObject* key_ = nullptr;
    PyObject* value_ = nullptr;
    std::cout <<"\n(key: value): ";
    while (PyDict_Next(f_PyDict, &pos, &key_, &value_))
    {
        /// 先打印关键字
        if (PyUnicode_Check(key_))  // NOLINT
        {
            PyUnicode_AsWideChar(key_, buff, sizeof(buff));
            std::wcout << "(" << buff << ", ";
        }
        else if (PyLong_Check(key_))  // NOLINT
            std::cout << "(" << PyLong_AsLong(key_) << ", ";
        else
            std::cout << "(" << "Unknown" << ", ";

        /// 再打印值
        if (PyUnicode_Check(value_))  // NOLINT
        {
            PyUnicode_AsWideChar(value_, buff, sizeof(buff));
            std::wcout << buff << ")";
        }
        else if (PyLong_Check(value_))  // NOLINT
            std::wcout << PyLong_AsLong(value_) << ")";
        else
            std::wcout << "Unknown" << ")";
    }
    /// 清理
    Py_XDECREF(key_);
    Py_XDECREF(value_);
}


/// 清理(会自己判断传进来的 PyObject指针是否是空,空就不做操作)
Py_XDECREF(f_PyDict);

六、完整实例程序

#include <Python.h>

void testBasicType();

int main()
{
    /// 设置Python的Home路径
    Py_SetPythonHome(L"./");
    /// Python解释器初始化
    Py_Initialize();

    /// 测试 在 C++ 中创建 和 转化 Python 中的基本类型
    testBasicType();


    /// 最后清理Python
    Py_Finalize();
    return 0;
}

上面只是 main 函数,测试函数 testBasicType() 完全是由上面6小段代码组成的,这里就不放了,下面 给出项目代码的 远程仓库地址:

https://gitee.com/mitchhong/cpp_-call_-python_-v1.git
---END---
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值