PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=091)

  本文发布于 2020-01-09 18:39:55,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=091)

环境说明

  本文所有实例环境为:win10+py35


  我们有个任务,需要在C++里面调用pycaffe的算法来做相关的检测。(不要问我为啥不直接用caffe的c++接口,因为后面还要调tensorflow和pytorch,导致了算法组为了统(偷)一(懒),就直接怼了一个pycaffe)。

  我还是第一次遇到这类问题,我去查了查,还真的有相关的内容,真的是存在即合理。

  本文大部分内容都是主要参考python官方手册,其次也看了网络上的一些资料,我这里做了一些汇总和衍生。(官方手册https://docs.python.org/zh-cn/3/c-api/index.html)

  阅读本文需要一定的c++基本‘’姿势‘’和了解一些最最最简单的py‘’姿势‘’。

Python C Hello World


  先来一个全世界通用的例子。(没做异常处理)


#ifdef __cplusplus
extern "C"{
#endif //
/*
Note Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included.
It is recommended to always define PY_SSIZE_T_CLEAN before including Python.h. See Parsing arguments and building values for a description of this macro.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h> #ifdef __cplusplus
}
#endif // #include <iostream>
int main(int argc, char * argv[]){ //This function works like Py_Initialize() if initsigs is 1.
//If initsigs is 0, it skips initialization registration of signal handlers, which might be useful when Python is embedded.
Py_InitializeEx(1);
PyRun_SimpleString("print('hello world.')");
Py_Finalize();
#ifdef _WIN32 || _WIN64 system("pause"); #elif __linux__ || __linux #endif //
return 0;
}

  编译,设定python的头文件和库文件路径。(注意python的库名字),我这里是在win上做的演示,实际是部署到linux.相关结果如图:

Python C 常用接口


python 基本数据类型对象

  更加详细的,请查看官方doc中关于Py_BuildValue()接口的描述。

    PyObject * func_name = Py_BuildValue("s","test_add");
PyObject * arg1 = Py_BuildValue("l",3);
python 序列或者容器对象

  更多内容,详见官方doc

//tuple
PyObject* PyTuple_New(Py_ssize_t len)
//Return value: New reference.
//Return a new tuple object of size len, or NULL on failure. PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)
//Return value: Borrowed reference.
//Return the object at position pos in the tuple pointed to by p. If pos is out of bounds, return NULL and set an IndexError exception. int PyTuple_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o)
//Insert a reference to object o at position pos of the tuple pointed to by p. Return 0 on success. If pos is out of bounds, return -1 and set an IndexError exception.
//list
//原理同tuple
PyObject* PyList_New(Py_ssize_t len)
int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index)
//dict
//原理同tuple
PyObject* PyDict_New()
int PyDict_SetItem(PyObject *p, PyObject *key, PyObject *val)
PyObject* PyDict_GetItem(PyObject *p, PyObject *key)

  对于这些python中的数据类型对象,都有相关的c api来创建、操作,更多的内容可以查看官方doc。这部分内容较简单,不做示例。

python 模块对象
PyObject* PyImport_ImportModuleEx(const char *name, PyObject *globals, PyObject *locals, PyObject *fromlist)
/*
Return value: New reference.
Import a module. This is best described by referring to the built-in Python function __import__(). The return value is a new reference to the imported module or top-level package, or NULL with an exception set on failure. Like for __import__(), the return value when a submodule of a package was requested is normally the top-level package, unless a non-empty fromlist was given. Failing imports remove incomplete module objects, like with PyImport_ImportModule().
*/
PyObject* PyModule_GetDict(PyObject *module)
/*
Return value: Borrowed reference.
Return the dictionary object that implements module's namespace; this object is the same as the __dict__ attribute of the module object. If module is not a module object (or a subtype of a module object), SystemError is raised and NULL is returned. It is recommended extensions use other PyModule_*() and PyObject_*() functions rather than directly manipulate a module's __dict__.
*/

  这些api主要是导入python模块,类似关键字import,同时返回模块的属性dict。这些属性包含当前命名空间下的:全局变量,全局函数,类等等。

python callable object , python class object, python object function
/*
Return value: New reference.
Call a callable Python object callable, with arguments given by the tuple args. If no arguments are needed, then args can be NULL. Return the result of the call on success, or raise an exception and return NULL on failure. This is the equivalent of the Python expression: callable(*args).
*/
PyObject* PyObject_CallObject(PyObject *callable, PyObject *args) /*
Return value: New reference.
Return a new instance method object, with func being any callable object func is the function that will be called when the instance method is called.
*/
PyObject* PyInstanceMethod_New(PyObject *func) //call obj.func
PyObject* PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)
PyObject* PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ..., NULL)

PyObject 对象


  这个对象是pythonc c api 所有对象的wrapper,有许许多多需要注意的事项,其中最重要的就是其内存管理问题。详细细节参考官方doc,这里给出最重要的几条我遇到的结论:

  1. pyobject 是依靠引用计数来管理对象的。
  2. pyobject 的创建者需要对这个obj负责,创建者可以传递,存储,和调用 Py_DECREF()
  3. python c api 都有关于pyobject的处理说明,比如:借用,新建。如下图。

本章特别重要,有兴趣的去看官方文档。当你乱用Py_INCREF和Py_DECREF时,你写的程序会在调用Py_Finalize时崩溃,这个时候,你需要看下文,去看看你哪个地方用错了。(https://docs.python.org/3.8/extending/extending.html#ownership-rules)

实例


  上一个实例来展示本文所有内容。

  这是我要调用的python module

'''
'''
@Description:
@Author: Sky
@Date: 2019-12-05 16:23:29
@LastEditors : Sky
@LastEditTime : 2020-01-09 18:04:41
@FilePath: \Test_C_Call_Python\py_modules\py_normal_test.py
'''
import sys class TestPythonNormal:
cls_attr = 'I am cls attr'
# def __new__(class_ins):
# print('call TestPythonNormal.__new__')
# return class_ins
def __init__(self):
print('call TestPythonNormal.__init__')
self.obj_attr = 'I am obj attr' def test_add(self, a = 0 , b = 0):
print("a = ", a)
print("b = ", b)
print("a + b = ", (a + b))
return (a + b) g_num = 22222
g_str = 'hello f***' def g_func(a):
print("g_func arg = ", a - 1) if __name__ == '__main__':
test = TestPythonNormal()
test.test_add(2, 3)
print(TestPythonNormal.__dict__)
# print(TestPythonNormal.__dir__(test))
/*
* @Description:
* @Author: Sky
* @Date: 2020-01-09 11:26:28
* @LastEditors : Sky
* @LastEditTime : 2020-01-09 18:21:53
* @FilePath: \Test_C_Call_Python\test_tmp.cpp
* @Github:
*/
#ifdef __cplusplus
extern "C"{
#endif // #define PY_SSIZE_T_CLEAN
#include <Python.h> #ifdef __cplusplus
}
#endif // #include <iostream> int main(int argc, char * argv[]){ //This function works like Py_Initialize() if initsigs is 1.
//If initsigs is 0, it skips initialization registration of signal handlers, which might be useful when Python is embedded.
Py_InitializeEx(1);
PyRun_SimpleString("print('hello world.')"); //add path of module-py_normal_test to sys.path
PyRun_SimpleString("import sys");
PyRun_SimpleString("import numpy as np");
PyRun_SimpleString("sys.path.append('E://TestDir//Test_C_Call_Python//py_modules')");
PyRun_SimpleString("print(sys.path)"); //import
//Return value: New reference.
PyObject * p_module = PyImport_ImportModuleEx("py_normal_test", NULL, NULL, NULL);//import module //get module attr, they are g_var, g_func, class and so on.
//Return value: Borrowed reference.
PyObject * p_dict_mo = PyModule_GetDict(p_module);//get module's all attr
//Return value: Borrowed reference.
PyObject * p_module_g_num = PyDict_GetItemString(p_dict_mo, "g_num");// global var PyObject * p_module_cls = PyDict_GetItemString(p_dict_mo, "TestPythonNormal");//class PyObject * p_module_g_func = PyDict_GetItemString(p_dict_mo, "g_func");// global function //print g_var
std::cout<<"g_num is "<<PyLong_AsLong(p_module_g_num)<<std::endl; //call g_func
//Return value: New reference.
PyObject * arg_tuple = PyTuple_New(1);
PyTuple_SetItem(arg_tuple, 0, p_module_g_num);//prepare arg
PyObject_CallObject(p_module_g_func, arg_tuple);//call g_func //instance a class
//Return value: New reference.
PyObject * p_cls_instance = PyInstanceMethod_New(p_module_cls);//call builtin.__new__, new a instance
//Return value: New reference.
p_cls_instance = PyObject_CallObject(p_cls_instance, NULL);//call __init__,construct function,return None
// Py_XDECREF(p_none); //print class-var
//Return value: New reference.
//PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)
PyObject * cls_attr = PyObject_GetAttrString(p_module_cls, "cls_attr");
std::cout<<"cls_attr is "<<PyUnicode_AsUTF8(cls_attr)<<std::endl; //print obj.var
PyObject * obj_attr = PyObject_GetAttrString(p_cls_instance, "obj_attr");
std::cout<<"obj_attr is "<<PyUnicode_AsUTF8(obj_attr)<<std::endl; //call obj.func
//Way0: This is the equivalent of the Python expression: obj.name(arg1, arg2, ...).
//Return value: New reference.Return value: New reference.
PyObject * ret0 = PyObject_CallMethod(p_cls_instance, "test_add", "ii", 10, 10);//test_add(self, 10, 10)
std::cout<<"call obj.func, Way0 ret = "<<PyLong_AsLong(ret0)<<std::endl; //Return value: New reference.
PyObject * func_name = Py_BuildValue("s","test_add");//
PyObject * arg1 = Py_BuildValue("l",3);
PyObject * arg2 = Py_BuildValue("l",3);
//Return value: New reference.
PyObject * ret1 = PyObject_CallMethodObjArgs(p_cls_instance, func_name, arg1, arg2, NULL);//test_add(self, 3, 3)
PyErr_PrintEx(1);
std::cout<<"call obj.func, Way1 ret = "<<PyLong_AsLong(ret1)<<std::endl; Py_XDECREF(ret1);
Py_XDECREF(arg2);
Py_XDECREF(arg1);
Py_XDECREF(func_name);
Py_XDECREF(ret0);
Py_XDECREF(obj_attr);
Py_XDECREF(cls_attr);
Py_XDECREF(p_cls_instance);
Py_XDECREF(p_module);
//Py_XDECREF(ret0); Py_Finalize();
#ifdef _WIN32 || _WIN64 system("pause"); #elif __linux__ || __linux #endif //
return 0;
}

  效果:

  以下的内容希望重点关注:

  1. 其实这个看起来是很简单的,如果有一定的c++基础,整个流程就是导入模块,获取模块中的属性,这些属性包括函数,全局变量,类等等。
  2. 操作全局的函数和变量
  3. 这里最难的还是对于python class 的操作,第1点中获取的类属性是一个callable object,这个时候需要实例化class(这里调用了builtin的__new__,而不是class中重载的__new__),然后需要手动调用__init__方法。到此,class的实例化完成
  4. 通过api获取class的属性,就是class的变量
  5. 通过api调用object method,传入obj和函数名,包括参数,直接调用即可,主要某些api要关注self这个参数。

调试常用手段


  1. 如果Py_Finalize()调用时,崩溃了,请检查自己的Py_INCREF和Py_DECREF各个引用增减是否正确。
  2. 如果某些python c api调用失败了,可以尝试使用 PyErr_PrintEx(1);

后记


  无

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

C++ 调用 Python 总结(一)的更多相关文章

  1. cpp 调用python

    在用cpp调用python时, 出现致命错误: no module named site  ,  原因解释器在搜索路径下没有找到python库.可以在调用Py_Initialize前,调用 Py_Se ...

  2. c调用python

    #include <Python.h>//python33(python2.x有几个函数不对应) /* PyImport_ImportModule 导入一个Python模块并返回它的指针 ...

  3. linux+php+apache web调用python脚本权限问题解决方案

    lamp : linux + apache + mysql + php 在上篇随笔中linux+php+apache调用python脚本时出现的问题的根本原因是:apache运行时使用的apache用 ...

  4. linux+php+apache web调用python脚本权限问题

    lamp : linux + apache + mysql + php 在近期项目中使用 linux + apache + php调用python脚本是出现以下权限问题: build/bdist.li ...

  5. C#中调用python方法

    最近因为项目设计,有部分使用Python脚本,因此代码中需要调用python方法. 1.首先,在c#中调用python必须安装IronPython,在 http://ironpython.codepl ...

  6. PHP 调用Python脚本

    上次做用户反馈自动翻译,写了个python脚本,将日文的用户反馈翻译成中文,效果虽然可以,但其它不懂python的童鞋就没法使用了,所以搭了个web服务,让其他人可以通过网页访问查询.使用的是apac ...

  7. C++中调用Python脚本

    C++中调用Python脚本的意义就不讲了,至少你可以把它当成文本形式的动态链接库, 需要的时候还可以改一改,只要不改变接口, C++的程序一旦编译好了,再改就没那么方便了 先看Python的代码 代 ...

  8. java调用python代码

    同样的我们需要安装jython,具体的步骤如下: 1. 去 http://sourceforge.net/projects/jython/ 下载最新的jython相关的jar包. 2. 下载下来的ja ...

  9. C++调用python

    本文以实例code讲解 C++ 调用 python 的方法. 本文在util.h中实现三个函数: 1. init_log: 用google log(glog)初始化log 2. exe_command ...

  10. C#调用Python 脚本语言

    1. 安装IronPython http://pan.baidu.com/s/1qW4jNJ2  下载IronPython 2.7 安装下载下来的安装包 2. 创建项目 创建一个C#的Windows窗 ...

随机推荐

  1. ssh原理及使用场景

    用过linux系统的朋友,基本肯定会用过ssh.因为大部分的linux登录都是通过ssh将进行登录,除非你用的是类似windows的桌面版. 一.什么是SSH SSH 为 Secure Shell 的 ...

  2. Matrix【未完成】

    Matrix The fitrst thing we do,let's kill all the language lawyers. -- Henry VI, Part II The "pr ...

  3. Linux-特殊权限设置(SUID、SGID、SBIT)

    一.SUID权限 1.概念 当s这个标志出现在文件所有者的x权限上时,例如文件权限状态"-rwsr-xr-x",此时就被称为Set UID,简称SUID. 如果该属主权限位上有执行 ...

  4. 内核5.4以上, Realtek 8111网卡初始化失败

    在Centos7中, 升级内核到5.4.x或5.11.x时, 都会出现realtek8111网卡无法启动的问题, 在dmesg中能看到这个错误 $ dmesg |grep -i r8169 ... r ...

  5. Swoole从入门到入土(25)——多进程[进程间无锁计数器]

    Atomic 是 Swoole 底层提供的原子计数操作类,可以方便整数的无锁原子增减.原子计数器有如下特点: - 使用共享内存,可以在不同的进程之间操作计数 - 基于 gcc/clang 提供的 CP ...

  6. 文心一言 VS 讯飞星火 VS chatgpt (199)-- 算法导论15.2 1题

    一.用go语言,对矩阵规模序列(5,10,3,12,5,50,6),求矩阵链最优括号化方案. 文心一言,代码正常运行: 在Go语言中,为了找到矩阵链乘法的最优括号化方案,我们通常会使用动态规划(Dyn ...

  7. win32 - 找出占用文件的进程id和name

    日常文件操作的时候,在删除或者移动某个文件的时候,发现它被某些进程占用了. 那么下面的代码就可以帮助我们找出这些进程的id和name. 原理: 将资源注册到Restart Manager会话.重新启动 ...

  8. dart的map方法如何获取index

    一.前言 我们常常用dart中的map方法遍历List,但是直接用map,只能取到value,得不到index,这是因为map方法就只给了一个value,map的实现如下图: 下面就看看获取index ...

  9. 第132篇:npm第一次使用自己的包(package-lock.json、package.json文件作用说明)

    好家伙,   1.新建一个文件夹,命名为test   2.下载包 npm i panghu-planebattle   空白的文件夹中多了两个文件 package-lock.json和package. ...

  10. 【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具

    问题描述 当应用部署到微软云 Azure后,如果遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的时候,有什么好的调试方法呢? 来判断具体时那一段请求耗时呢? 问题解答 当然 ...