python语言拥有完善的算法库,各种模型都有相应的python实现,但由于全局锁GIL的存在,python不能真正的实现多线程的功能,Java和C++ 等程序开发语言有着成熟的并发、并行功能。目前人工智能算法是解决应用系统中的某个问题,就整个应用系统而言不太可能全部用python开发,从其他程序语言调用python是一个较为普遍需求,通常将python 作为一个微服务提供给其他程序语言使用。
一、C/C++调用python
python作为一种解释性语言有多种实现方式,其中常用的是用C语言编写的cpython,C/C++调用Python方法手段是最为丰富的,以Visual Studio 2019作为开发平台为例,新建一个c++程序项目,将python安装目录下的include文件夹下所有文件复制到项目目录。
在IDE设置链接器,附加依赖项选择相应的静态库,以python3.8为例:
需要注意,平台必须和安装的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聚类算法 |
评论区 |