目前系统接到一个需求,希望平台能支持运行python脚本,而我们的平台是由java编写的,所有我们
需要预言出我们最终的实现方案
经过调研,目前java调用python脚本主要有以下几种方式
1.使用jython
Jython是Python编程语言的JVM实现。 它旨在在Java平台上运行。 Jython程序可以导入和使用任何Java类。 就像Java一样,Jython程序编译为bytecode 。 其中一个主要优点是用Python设计的用户界面可以使用AWT , Swing或SWT Package GUI元素。
Jython以JPython开头,后来被重命名,紧跟着Guido Van Rossum创建的标准Python实现CPython 。 Jython由Jim Hugunin于1997年创立。 Jython 2.0于1999年发布。从那时起,Jython 2.x版本对应于等效的CPython版本。 2015年5月发布的Jython 2.7.0对应于CPython 2.7。 Jython 3.x的开发正在进行中。
使用方式如下:
先引入jar包
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
</dependency>
Map<String, Object> binding = new HashMap<>();
binding.put("name", "zhangshan");
binding.put("age", "18");
try (PythonInterpreter interpreter = new PythonInterpreter(Py.java2py(binding))) {
interpreter.exec("varMap = globals()");
interpreter.exec("varMap.put('name', 'ls')");
interpreter.exec("varMap.put('sex', '1')");
PyObject vars = interpreter.get("varMap");
System.out.println(vars.toString());
}
这种方式的好处是:
- 无需依赖python环境
- 能方便的对参数进行绑定,适合java程序与python脚本中存在参数交互
- 无需将脚本生成文件管理,直接执行脚本字符串即可
缺点也很明显:
- 最高只支持到python2.7版本
- 对C语言编写的一些依赖包无法支持
2.使用命令行方式
java.lang.Runtime.exec(String command, String[] envp, File dir) 方法在指定环境和工作目录的独立进程中执行指定的字符串命令。这是一个方便的方法。exec(command, envp, dir)调用行为完全相同于调用 exec(cmdarray, envp, dir),其中cmdarray是命令的所有标记的数组。
更确切地说,该命令串被分成使用由调用new StringTokenizer(command) 与字符的类别没有进一步的修改创建StringTokenizer令牌。由标记生成器生成的令牌,然后以相同的顺序放置在新的字符串数组cmdarray。
使用方式如下:
首先先在宿主机安装python环境
int a = 18;
int b = 23;
try {
String[] args1 = new String[] { "python", "/Users/rayduan/PycharmProjects/pythonProject/test.py", String.valueOf(a), String.valueOf(b) };
Process proc = Runtime.getRuntime().exec(args1);// 执行py文件
BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
proc.waitFor();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
这种方式的好处显而易见:
- 可以支持各种版本的python
- 支持各种形式的第三方包
但是缺点也不少:
- 无法和java程序交换参数
- 想要得到返回结果,需要使用print将返回结果输出,改变用户使用函数的方式
if __name__ == '__main__':
print(sys.path)
- 需要将脚本维护成py文件进行管理
- 不同系统上,命令的兼容性
- Runtime.exec()的command参数只是一个可运行的命令或者脚本,并不等效于Shell解器或者Cmd.exe,如果你想进行输入输出重定向,pipeline等操作,则必须通过程序来实现。不能直接在command参数中做
- Runtime.exec()可能hang住,甚至死锁(Runtime.exec()创建的子进程公用父进程的流,不同平台上,父进程的stream buffer可能被打满导致子进程阻塞,从而永远无法返回。)
3.python直接调用python脚本(通过暴露接口的方式)
#!/usr/local/bin/python3.7
import time
import os
count = 0
str = ('python b.py')
result1 = os.system(str)
print(result1)
while True:
count = count + 1
if count == 8:
print('this count is:',count)
break
else:
time.sleep(1)
print('this count is:',count)
print('Good Bye')
os.system()方法在子shell中执行命令(字符串)。该方法是通过调用标准C函数system()来实现的,并且具有相同的限制。如果命令生成任何输出,则将其发送到解释器标准输出流。无论何时使用此方法,都将打开操作系统的相应 shell 并在其上执行命令.遗憾的是它并没有返回值。
python另外有通过shell命令行调用python文件的方式,流程和java的Runtime.exec类似。
4.使用jep
Jep使用JNI和CPython API来启动JVM中的Python解释器。当你在Java中创建一个Interpreter实例时,将为该Java Interpreter实例创建一个Python解释器,并保留在内存中,直到用Interpreter.close()关闭该Interpreter实例。由于需要管理一致的Python线程状态,创建Interpreter实例的线程必须在对该Interpreter实例的所有方法调用中重复使用。Jep应该与任何纯Python模块一起工作。它可以与各种CPython扩展一起工作。开发人员报告说,Jep可以与NumPy、Scipy、Pandas、TensorFlow、Matplotlib、cvxpy等一起工作。
使用方式如下:
首先我们需要python运行环境,jep能很好的支持python2.0以及3.0,这里我们以3.8为例(jep目前最高支持python3.8,框架还在持续更新中)。
- 安装好python运行环境后我们安装jep
pip3 install jep
- 配置jep的库名路径export LD_LIBRARY_PATH=“<your_user_path>/myenv/lib/python3.8/site-packages/jep”$LD_LIBRARY_PATH(如果不生效,则在java程序运行时指定库地址-Djava.library.path=/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/jep/)
- java程序引入jep的jar包
<dependency>
<groupId>black.ninia</groupId>
<artifactId>jep</artifactId>
<version>4.0.3</version>
</dependency
通过上述配置后我们就搭建好了jep的运行环境,使用起来也很简单
/**
* 从python中拿到变量值
* @param scriptContent
*/
public void runScriptWithReturn() {
try (SharedInterpreter interpreter = new SharedInterpreter()) {
interpreter.exec("import sys");
Object value = interpreter.getValue("sys.path");
System.out.println(value);
}
}
/**
* 调用python文件中的方法
*/
public void runScriptWithFileMethod() {
JepConfig jepConfig = new JepConfig().addIncludePaths("/Users/rayduan/PycharmProjects/pythonProject");
SharedInterpreter.setConfig(jepConfig);
try (Interpreter interpreter = new SharedInterpreter()) {
interpreter.exec("from demo import *");
Object result = interpreter.invoke("func", 1, 2);
System.out.println(result);
}
}
/**
* 通过python脚本修改变量中的值
*/
public void runScriptWithChangeParam() {
try (Interpreter interpreter = new SharedInterpreter()) {
Map<String, Object> binding = new HashMap<>();
binding.put("name", "zhangshan");
binding.put("age", "18");
interpreter.set("vars", binding);
interpreter.exec("vars['name'] = 'ls'");
Map vars = interpreter.getValue("vars", Map.class);
System.out.println(vars);
}
}
根据我们需求,jep能完全覆盖我们的使用场景,下面我们说下jep的优缺点。
优点:
- 无需将脚本生成文件管理,直接执行脚本字符串即可
- 可以支持各种版本的python
- 支持各种形式的第三方包
- 使用方便,能和java程序进行很好的交互
缺点:
- 搭建环境较为复杂
总结:
目前存在的需求是支持python3.0,同时我们的目前平台脚本功能主要是通过脚本去改变输入输出的参数或者场景运行时参数。综合上述观点,jep无疑是最好的选择