C/C++、Java调用python函数

python语言拥有完善的算法库,各种模型都有相应的python实现,但由于全局锁GIL的存在,python不能真正的实现多线程的功能,Java和C++ 等程序开发语言有着成熟的并发、并行功能。目前人工智能算法是解决应用系统中的某个问题,就整个应用系统而言不太可能全部用python开发,从其他程序语言调用python是一个较为普遍需求,通常将python 作为一个微服务提供给其他程序语言使用。

一、C/C++调用python

python作为一种解释性语言有多种实现方式,其中常用的是用C语言编写的cpython,C/C++调用Python方法手段是最为丰富的,以Visual Studio 2019作为开发平台为例,新建一个c++程序项目,将python安装目录下的include文件夹下所有文件复制到项目目录。

include.png

IDE设置链接器,附加依赖项选择相应的静态库,以python3.8为例:

linker.png

需要注意,平台必须和安装的python版本一致,如果python是64位的,Visual Studio 2019平台选项需选择为64位,创建一个python文件pym.py,写入两个简单函数,一个实现加法、一个实现矩阵相乘:

import  numpy as np
import sys
def add(a,b):
    return a + b
def mt(a,b):
    numpya=np.matrix(a)
    numpyb = np.matrix(b)
    print(numpya[0,1])
    ret=numpya*numpyb
    print(ret)
    return ret.tolist()

也可以将pym.py文件编译为pyc文件格式,这样可以实现文件内容不以明文出现,以调用add函数为例,C++代码如下:

#include <iostream>
#include <stdlib.h>
#include <iostream> 
#include <windows.h>
#include <wchar.h>
#include "include\Python.h" 
using namespace std;
 
int testadd() {
        Py_Initialize();  
        PyRun_SimpleString("import sys");  
        PyRun_SimpleString("sys.path.append('./')");  
        //导入模块  
        PyObject* pModule = PyImport_ImportModule("pym"); 
        if (!pModule)
        {
            cout << "Python get module failed." << endl;
            Py_DECREF(pModule);
            return 0;
        }
        //获取模块内add函数  
        PyObject* pmethodadd = PyObject_GetAttrString(pModule, "add");
        if (!pmethodadd || !PyCallable_Check(pmethodadd))
        {
            cout << "Can't find funftion (_add)" << endl;
            return 0;
        } 
        //初始化要传入的参数,args配置成传入两个参数的模式  
        PyObject* args = PyTuple_New(2);
        //将Long型数据转换成Python可接收的类型  
        PyObject* arg1 = PyLong_FromLong(4);
        PyObject* arg2 = PyLong_FromLong(3);

        //将arg1配置为arg带入的第一个参数  
        PyTuple_SetItem(args, 0, arg1);
        //将arg1配置为arg带入的第二个参数  
        PyTuple_SetItem(args, 1, arg2);

        //传入参数调用函数,并获取返回值  
        PyObject* pRet = PyObject_CallObject(pmethodadd, args); 
        if (pRet)
        {
            //将返回值转换成long型  
            long result = PyLong_AsLong(pRet);
            cout << "result:" << result << endl;
        }

        Py_DECREF(pRet);
        Py_DECREF(args);
        Py_DECREF(pmethodadd);
        Py_DECREF(pModule); 
        Py_Finalize();

}
int main()
{ 
      testadd(); 
     
}

调用python中矩阵乘法的示例程序,python函数利用numpy实现两个矩阵相乘:

#include <iostream>
#include <stdlib.h>
#include <iostream> 
#include <windows.h>
#include <wchar.h>
#include "include\Python.h" 
using namespace std;
 
int  testmatrix() {
    Py_Initialize();
    PyRun_SimpleString("import sys");
    //添加Insert模块路径  
    //PyRun_SimpleString(chdir_cmd.c_str());
    PyRun_SimpleString("sys.path.append('./')");

    //导入模块  
    PyObject* pModule = PyImport_ImportModule("pym");

    if (!pModule)
    {
        cout << "Python get module failed." << endl;
        return 0;
    } 
    PyObject* pmt = PyObject_GetAttrString(pModule, "mt"); 
        long CArray1[2][2] = { {2,3},{5,6} };
        long CArray2[2][2] = { {0,-1},{3,11} };

        PyObject* PyList2d_1 = PyList_New(0);
        PyObject* PyList2d_2 = PyList_New(0); //定义该PyList对象为0和PyList_Append有关,相当于定义PyList为[]
        PyObject* ArgList2d = PyTuple_New(2);
        for (int i = 0; i < 2; i++) {
            PyObject* PyList1 = PyList_New(2);
            PyObject* PyList2 = PyList_New(2);
            for (int j = 0; j < 2; j++) {
                //PyLong_FromLong是新引用,但PyList_SetItem是偷引用,所以不需要DECREF PyLong_FromLong对象
                PyList_SetItem(PyList1, j, PyLong_FromLong(CArray1[i][j]));
                PyList_SetItem(PyList2, j, PyLong_FromLong(CArray2[i][j]));
            }
            PyList_Append(PyList2d_1, PyList1);
            PyList_Append(PyList2d_2, PyList2);
            Py_DECREF(PyList1);//尝试把这两句注释掉将引发内存泄露
            Py_DECREF(PyList2);
        }
        PyTuple_SetItem(ArgList2d, 0, PyList2d_1);
        PyTuple_SetItem(ArgList2d, 1, PyList2d_2); 
    
        PyObject* pReturn2d = PyObject_CallObject(pmt, ArgList2d);
        if (pReturn2d) {

            if (PyList_Check(pReturn2d)) {
                int SizeOfList = PyList_Size(pReturn2d);
                for (int i = 0; i < SizeOfList; i++) {
                    PyObject* ListItem = PyList_GetItem(pReturn2d, i);
                    int NumOfItems = PyList_Size(ListItem);
                    for (int j = 0; j < NumOfItems; j++) {
                        PyObject* Item = PyList_GetItem(ListItem, j);
                        int result;
                        PyArg_Parse(Item, "i", &result);//i表示转换成int型变量
                        printf("%d  ", result);
                    }
                    cout << endl;
                }

            }
        }

     Py_DECREF(ArgList2d); 
     Py_DECREF(pReturn2d);  
    Py_DECREF(pmt);
    Py_DECREF(pModule);
    Py_Finalize();
}
int main()
{ 
      testmatrix(); 
     
}

应当注意示例代码中的Py_DECREF函数,Py_DECREF目的是将python对象引用数减一,python利用引用计数来实现垃圾收集机制,C++创建的PObject对象必须由cpython来释放,当一个PObject对象引用数为0时即可释放该对象占用的内存,C++使用完一个PObject对象如果没有减少引用计数,会造成内存泄露,引用计数是C++调用Python一个难点。

但并不是每个PObject对象都需要调用Py_DECREF函数实现引用数减一,python将C/C++调用PObject对象分为三种形式,分别是New References,Stolen References,Borrowed References,可暂翻为新引用、偷引用、借引用。

新引用:此时PObject对象所有权转移到了C++环境中,用完该对象时调用Py_DECREF函数减少引用数,以函数返回形式出现的PObject对象一般为新引用类型。

偷引用:典型的函数是设置参数函数PyList_SetItem(PyObject* list, index, PyObject* item),使用该函数后item的所有权被PyList_SetItem函数偷走,由PyList_SetItem函数维护引用数的统计,在C++环境中不需要调用Py_DECREF函数。这很好理解,PyList_SetItem是为了配合PyTuple_New函数使用的,用于设置每一个参数值,PyTuple_New函数是一个新引用,当调用结束必须手工调用Py_DECREF函数减少PyTuple_New函数返回PObject对象的引用数,如示例代码中args,而args可视为PyList_SetItem(PyObject* list, index, PyObject* item)中item的容器,当args销毁时,由python在内部自动减少item引用数,item被PyList_SetItem偷走了,由偷引用的存在其实方便了C/C++中的开发,不用手工的去维护每个参数的计数。

借引用:借引用一般出现在查看某个PObject对象时,如示例代码中从一个列表中取一个成员:

 PyObject* ListItem = PyList_GetItem(pReturn2d, i);

Listitem为借引用,是原来对象的一个拷贝,所有权没有发生转移而导致计数增加,借引用不需要调用Py_DECREF函数减少计数。计数问题是C++调用Python函数经常遇到的问题,在实际开发中可多迭代几次,观察程序的内存变化,如果内存占用不断变大,一般都是发生了内存泄漏。

二、Java调用Python函数

Java调用python有多种方式,可以利用Python的java实现jython来调用:

import org.python.util.PythonInterpreter;public class FirstJavaScript {
    public static void main(String args[]) {

        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("days=('mod','Tue','Wed','Thu','Fri','Sat','Sun'); ");
        interpreter.exec("print days[1];");

    }// main}

这种方式好处是简单方便,但缺点是jython中第三方库很少,实际应用中没有太大价值。

另一种方式是利用C++调用python,再将C++实现封装为动态链接库,java使用jna或jni方式调用动态链接库,windows中动态链接库是dll形式,而在linux中则是.so文件,此种方式是将C/C++作为Java和Python之间的桥梁。

推荐的第三种方式是利用JSONRPC实现Java调用Python,Python有完善的RPC框架,如flask、tornado等RPC库,以flask为例,首先python端代码 PyService.py:

from flask import Flask,json
from flask_jsonrpc import JSONRPC
import multiprocessing
import time
import configparser
from RequestItem import RequestItem
from multiprocessing import  Queue
from HashMap import  HashMap
import memcache
QUEUEMAXSIZE=10
PROCESSNUM=5
WAITINTERVAL=0.1
SEVERPORT=5588
MEMCACHEPORT=11211
MEMCACHESERVER='127.0.0.1'
MEMCACHDUR=300
HOLDWAITMAX=300
TASK_WAIT=0.5
TASK_SLEEP=0.1
app = Flask(__name__)
mods=HashMap()
config = configparser.ConfigParser()
path = r'config.ini'
config.read(path)
if (config['setting']['proccessnum']):
    PROCESSNUM = int(config['setting']['proccessnum'])
if (config['setting']['queuesize']):
    QUEUEMAXSIZE = int(config['setting']['queuesize'])
if (config['setting']['callwaitinteval']):
    WAITINTERVAL = float(config['setting']['callwaitinteval'])
if (config['setting']['serverport']):
    SEVERPORT = int(config['setting']['serverport'])
if (config['setting']['memcacheport']):
    MEMCACHEPORT = int(config['setting']['memcacheport'])
if (config['setting']['taskwait']):
    TASK_WAIT = float(config['setting']['taskwait'])
if (config['setting']['tasksleep']):
    TASK_SLEEP = float(config['setting']['tasksleep'])
if (config['setting']['memcacheserver']):
    MEMCACHESERVER =  config['setting']['memcacheserver']
if (config['server']['memdurate']):
    MEMCACHDUR = int(config['server']['memdurate'])
if (config['server']['holdwait']):
    HOLDWAITMAX = int(config['server']['holdwait'])

mc=memcache.Client([MEMCACHESERVER+":" + str(MEMCACHEPORT)], debug=False)
jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True)
@jsonrpc.method(name='wg.action')
def Action(modname:str,method:str,*args:object)->object:
    if ( not QueueRecive.full() ):
        req = RequestItem(modname, method, *args)
        QueueRecive.put(req, block=True)
    ticktok=0
    while (mc.get(str(req.hashcode)) == None and ticktok<HOLDWAITMAX ):
        time.sleep(WAITINTERVAL)
        ticktok=ticktok+1
    if(mc.get(str(req.hashcode)) == None ):
        return   '{"status":"error","message":"timeout error"}'
    else:
        ret = mc.get(str(req.hashcode))
        mc.delete(str(req.hashcode))
        return ret


def loaddata(modname:str,method:str,*args:object)->object:
  try:
    if (  mods.get(modname)==None):
        mods.put( modname,   __import__('mods.'+modname, fromlist=True))
    mod=mods.get(modname)
    if (mod):
      if (hasattr(mod, method)):
        func = getattr(mod, method)
        return '{"status":"ok","message":"'+str(func(*args))+'"}'
      else:
        return '{"status":"error","message":"no method:'+method+'"}'
  except Exception  as e  :
    return '{"status":"error","message":"'+str(e)+'"}'
  else:
    return '{"status":"error"}'

def worker( q:multiprocessing.Queue,event:multiprocessing.Event,canrun,pa):
    try:
        while canrun.value:
            event.wait(TASK_WAIT)
            if(not q.empty()):
                #print('%s处理'%(pa))
                req:RequestItem = q.get(block=False)
                mc.set(str(req.hashcode), loaddata(req.modname,req.method,*req.args),time=MEMCACHDUR)
            else:
                time.sleep(TASK_SLEEP)
            event.set()
    finally:
        return

if __name__ == '__main__':
  manager = multiprocessing.Manager()
  event = multiprocessing.Event()
  canrun = multiprocessing.Value('b', True)
  QueueRecive = manager.Queue(maxsize=QUEUEMAXSIZE)
  for i in range(PROCESSNUM):
    process = multiprocessing.Process(target=worker, args=(  QueueRecive, event, canrun, 'work_' + str(i + 1),))
    process.start()
  app.run(host='0.0.0.0', port=SEVERPORT, debug=False)

服务启动前需读程序目录下config.ini完成配置,config.ini文件如下:

请点击下面广告后阅读余下文章
上一篇  生成对抗网络 下一篇 K-Means聚类算法
评论区