工欲善其事,必先利其器


文章目录

  • python 2.7
  • 升级python 2.7到3.6
  • 准备
  • 编译&&安装
  • 软连接
  • .pro添加
  • 静态库
  • gcc -ldl 选项作用
  • 动态链接库
  • 准备
  • 工程中配置
  • demo && 解析
  • C++调用py脚本
  • C++向python传递参数
  • 创建元组
  • 示例
  • 格式化字符
  • 转换Python的返回值
  • python调用C/C++
  • python2的方法
  • python3的方法
  • reference


python 2.7

.pro文件添加

INCLUDEPATH += -I /usr/include/python2.7
LIBS += /usr/lib/x86_64-linux-gnu/libpython2.7.so

升级python 2.7到3.6

python -V 查看版本

准备

(1)首次安装需要先安装zlib*

sudo apt-get install zlib*

(2)下载并解压
官网下载

wget http://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz

我把下载的.tgz移到software,再打开终端输入命令解压

tar -xzvf Python-3.6.6.tgz

wget是一个下载文件的工具,用在命令行下。未指定目录情况下所下载的文件在home目录下

编译&&安装

在/usr/local目录新建一个文件夹python3

sudo -s
mkdir /usr/local/python3

到解压后的目录

./configure -prefix=/usr/local/python3
make
make install

加上–enable-shared和-fPIC之后可以将python3的动态链接库编译出来

./configure --prefix=/usr/local/python3 --enable-shared CFLAGS=-fPIC

软连接

先修改一下老版本的连接,修改后命令行输入python_old2 -V,会看到旧版本的版本号

mv /usr/bin/python /usr/bin/python_old2

为python3创建新的软连接

ln -s /usr/local/python3/bin/python3 /usr/bin/python

python -V,可以看到升级成功了

qt开发 qt和python混编 qt python 混合编程_python


[python3采用编译动态链接库后]

python -V报错

python: error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory

复制该.so到/usr/lib

cp /usr/local/python3/lib/libpython3.6m.so.1.0  /usr/lib

后面才发现已经有python 3.5了

qt开发 qt和python混编 qt python 混合编程_python_02

.pro添加

静态库

libpython3.6m.a,本方法有个报错没解决,故最终选用动态链接库
Qt Creator右键工程目录,Add library

DISTFILES += \
    build-eye_tracking/testPython.py

unix:!macx: LIBS += -L$$PWD/../../../../usr/local/python3/lib/ -lpython3.6m

INCLUDEPATH += $$PWD/../../../../usr/local/python3/include/python3.6m
DEPENDPATH += $$PWD/../../../../usr/local/python3/include/python3.6m

unix:!macx: PRE_TARGETDEPS += $$PWD/../../../../usr/local/python3/lib/libpython3.6m.a

/usr/bin/ld: /home/ccl/Projects/eye_tracking/…/…/…/…/usr/local/python3/lib//libpython3.6m.a(dynload_shlib.o): undefined reference to symbol 'dlsym@@GLIBC_2.2.5’
-ldl没解决

gcc -ldl 选项作用

如果你的程序中使用dlopen、dlsym、dlclose、dlerror 显示加载动态库,需要设置链接选项 -ldl //如果动态库使用g++编译,那么动态库定义函数的时候加上extern “C”,否则会提示undefined symbol错误。

加载动态链接库,首先为共享库分配物理内存,然后在进程对应的页表项中建立虚拟页和物理页面之间的映射。

你可以认为系统中存在一种引用计数机制, 每当一个进程加载了共享库(在该进程的页表中进行一次映射),引用计数加一;

一个进程显式卸载(通过dlclose等)共享库或进程退出时,引用计数减 一,

当减少到0时,系统卸载共享库。

(1)打开动态链接库:dlopen,函数原型void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。

(2)取函数执行地址:dlsym,函数原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。

(3)关闭动态链接库:dlclose,函数原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

(4)动态库错误函数:dlerror,函数原型为: const char *dlerror(void);
当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

//lib/x86_64-linux-gnu/libdl.so: error adding symbols: DSO missing from command line
追加 -lavutil 库便解决

动态链接库

准备

由于Qt中的slots关键字与python重复,这里我们需要修改一下文件
/usr/local/python3/include/python3.6m/object.h,从

typedef struct{
    const char* name;
    int basicsize;
    int itemsize;
    unsigned int flags;
    PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;

(用gedit查找PyType_Spec)改为

typedef struct{
    const char* name;
    int basicsize;
    int itemsize;
    unsigned int flags;
    #undef slots // 这里取消slots宏定义
    PyType_Slot *slots;/* terminated by slot==0. */
    #define slots Q_SLOTS // 这里恢复slots宏定义与QT中QObjectDefs.h中一致
} PyType_Spec;
工程中配置

(1).pro添加

INCLUDEPATH += -I /usr/local/python3/include/python3.6m
LIBS += /usr/local/python3/lib/libpython3.6m.so

(2)头文件

#include <Python.h>

demo && 解析

C++调用py脚本


qt开发 qt和python混编 qt python 混合编程_qt开发 qt和python混编_03

Add New…新建一个python文件,移到工程的构建目录下,/home/ccl/Projects/eye_tracking/build-eye_tracking

#!/usr/bin/env python
# -*- coding: utf-8 -*-
def usePython():
    print("2020/2/7")
    print("Welcome to use Python in Qt")
def add(a,b):
    print(a+b)
    return a+b
//初始化Python解释器
Py_Initialize();
if (!Py_IsInitialized()) {
    return;
}
/*************************************
 * PyRun_SimpleString
把输入的字符串作为Python代码直接运行,返回0表示成功,-1表示有错。
大多时候错误都是因为字符串中有语法错误。
**************************************/
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");//把当前路径加入python的默认搜索路路径

//导入一个Python模块,参数name可以是*.py文件的文件名,相当于import
PyObject* pModule = PyImport_ImportModule("testPython");//导入要调用的testPython.py
if (!pModule) {
    qDebug()<<"cant open python file";
    return;
}
/*************************************
 * PyObject* PyObject_GetAttrString(PyObject *o, char*attr_name)
返回模块对象o中的attr_name 属性或函数,相当于Python中表达式语句,o.attr_name
**************************************/
PyObject* pFunHello = PyObject_GetAttrString(pModule,"usePython");
if (!pFunHello) {
    qDebug("get function hello failed");
    return;
}
//调用Python函数
PyObject_CallFunction(pFunHello,NULL);

PyObject* pFunAdd = PyObject_GetAttrString(pModule,"add");
if (!pFunAdd) {
    qDebug("get function add failed");
    return;
}

//两种调用方式
PyObject_CallFunction(pFunAdd,"(i,i)",5,7);
PyObject* pRet = PyEval_CallObject(pFunAdd,Py_BuildValue("(ii)",5, 7));
int result=0;
//解析python返回值
PyArg_Parse(pRet,"i", &result);
qDebug()<<"result="<<result;

//释放Python解释器所占用的资源
Py_Finalize();

C++向python传递参数

C++向Python传参数是以元组(tuple)的方式
(1)PyEval_CallObject,参数为PyObject*
PyObject* PyEval_CallObject(PyObject* pfunc, PyObject* pargs)

(2)PyObject_CallFunction,直接设置格式和参数
PyObject * PyObject_CallFunction(PyObject *callable_object,const char *format, …);

PyObject_CallFunction(pFunAdd,"(i,i)",5,7);
PyEval_CallObject(pFunAdd,Py_BuildValue("(ii)",5, 7));
创建元组

(1)PyTuple_New

//用PyTuple_New()创建一个元组
PyObject* pyParams = PyTuple_New(2);
//设置参数
PyTuple_SetItem(pyParams,0, Py_BuildValue("i",5));
PyTuple_SetItem(pyParams,1, Py_BuildValue("i",7));
PyEval_CallObject(pFunc, pyParams);

(2)PyObject* Py_BuildValue(char *format, …)
直接使用Py_BuildValue构建

PyObject* pyParams = Py_BuildValue("(ii)",5, 7);
示例
Py_BuildValue("")   					//None
Py_BuildValue("i",123)              	//123
Py_BuildValue("iii",123, 456, 789)  	//(123, 456, 789)

Py_BuildValue("s","hello")          	//'hello'
Py_BuildValue("ss","hello", "world")    //('hello', 'world')
Py_BuildValue("s#","hello", 4)          //'hell'

Py_BuildValue("()")                     //()
Py_BuildValue("(i)",123)                //(123,)
Py_BuildValue("(ii)",123, 456)          //(123, 456)
Py_BuildValue("(i,i)",123, 456)         //(123, 456)
Py_BuildValue("((ii)(ii))(ii)",1, 2, 3, 4, 5, 6)         //(((1, 2), (3, 4)), (5, 6))

Py_BuildValue("[i,i]",123, 456)        //[123, 456]

Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456)      //{'abc': 123, 'def': 456}
格式化字符


qt开发 qt和python混编 qt python 混合编程_qt开发 qt和python混编_04

qt开发 qt和python混编 qt python 混合编程_qt开发 qt和python混编_05

转换Python的返回值

python返回的都是PyObject对象,常用PyArg_Parse解析,指定格式。如果返回为元组,则使用PyArg_ParseTuple

int result=0;
//解析python返回值
PyArg_Parse(pRet,"i", &result);

python调用C/C++

python2的方法

官网Extending Python with C or C++

//C部分
#define min(a,b)    (((a) < (b)) ? (a) : (b))
char data[1024];
void SetData(const char *str)
{
	//把str的内容复制到data中
    strncpy(data, str, min(strlen(str) + 1, 1024));
}
const char *GetData()
{
    return data;
}

封装成Python调用的函数,就是增加调用时解析 python传进来的参数的功能,再调用C的函数

static PyObject* PySetData(PyObject *self, PyObject *args)
{
    const char* str = NULL;
    //解析出参数到str
    if ( !PyArg_ParseTuple(args, "s", &str) )
    {
        return 0;
    }
    SetData(str);
    Py_RETURN_NONE;
}
static PyObject* PyGetData(PyObject *self, PyObject *args)
{
    const char* str = NULL;
    return PyString_FromString(GetData());
}

PyMethodDef 是一个C结构体,用来完成一个映射,也就是便于方法查找,把需要被外面调用的方法都记录在这表内。
结构体成员说明:
第一个字段:在 Python 里面使用的方法名;
第二个字段:C 模块内的函数名;
第三个字段:方法参数类型,是无参数(METH_NOARGS) , 还是有位置参数(METH_VARARGS), 还是其他等等;
第四个字段:方法描述,就是通过 help() 或者 doc 可以看到的;
需要注意的是,这个列表的最后必须以 {NULL, NULL, 0, NULL} 的形式来代表声明结束

//函数注释,使用print(pycallc.py_set_data.__doc__)可以查看
PyDoc_STRVAR(PySetData_doc__, "\
测试\n\
\n\
PySetData(str)\n\
str: 出入的字符串\n\
返回: \n\
null \n\
");
PyDoc_STRVAR(PyGetData_doc__, "\
打印数据\n\
\n\
PyGetData()\n\
返回: \n\
data \n\
");

static PyMethodDef module_methods[] = {
    {"py_set_data", PySetData, METH_VARARGS, PySetData_doc__},
    {"py_get_data", PyGetData, METH_VARARGS, PyGetData_doc__},
    {NULL, NULL, 0, NULL}
    };

生成模块,python文件中使用import pycallc可以就可以使用了。

void InitCCallPy()
{
    //Py_InitModule3在python3已被弃用
    PyObject *module = Py_InitModule3("pycallc", module_methods,"python call c");
}

testPython.py里有import pycallc,需在前面InitCCallPy()生成

qt开发 qt和python混编 qt python 混合编程_动态链接库_06

testPython.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pycallc
def usePython():
    print("2020/2/7")
    print("Welcome to use Python in Qt")
def add(a,b):
    print(a+b)
    return a+b

def testC():
    pycallc.py_set_data("change hello world!")
    print 'in python : ',pycallc.py_get_data()
    print(pycallc.py_set_data.__doc__)
python3的方法

Python 3.x中不再使用Py_InitModule
官网Extending Python with C or C++

reference

linux qt 调用python简单例子Qt+Python混合编程 windows
Qt下调用python OSX系统
C++调用Python浅析

PS:
PyQt5