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聚类算法 |
| 评论区 | |