Python是一种越来越流行的编程语言,特别是在科学界,因为它有丰富的数字和统计包。因此,能够从Java应用程序中调用Python代码的需求并不少见。
在本教程中,我们将了解从Java调用Python代码的一些最常见方法。
运行环境
java version "1.8.0_191"
Python 3.8.3
准备工作
一个非常简单的python脚本,命名为hello.py
运行该脚本效果如下:
$ python hello.py
hello python
hello.py放在java项目的resources目录下。
实战部分
使用ProcessBuilder
public class ProcessBuilderDemo {
public static void main(String[] args) throws IOException {
ProcessBuilder processBuilder = new ProcessBuilder("python",
resolvePythonScriptPath("hello.py"));
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
List<String> results = readProcessOutput(process.getInputStream());
System.out.println("results = " + results);
}
private static String resolvePythonScriptPath(String filename) {
File file = new File("src/main/resources/" + filename);
return file.getAbsolutePath();
}
private static List<String> readProcessOutput(InputStream inputStream) throws IOException {
try (BufferedReader output = new BufferedReader(new InputStreamReader(inputStream))) {
return output.lines()
.collect(Collectors.toList());
}
}
}
其中需要注意的是redirectErrorStream(true)这个属性,如果出现任何错误,错误输出将与标准输出合并。如果我们没有将这个属性设置为true,那么我们将需要使用getInputStream()和getErrorStream()方法从两个单独的流读取输出。
程序执行结果
我们修改以下代码看看实际输出效果:
ProcessBuilder processBuilder = new ProcessBuilder("python",
resolvePythonScriptPath("hello2.py"));
processBuilder.redirectErrorStream(true);
运行结果执行不成功会打印出错误结果:
去掉redirectErrorStream(true)这行代码再运行控制台不会打印我们预期的结果,错误信息也不会输出。
ProcessBuilder processBuilder = new ProcessBuilder("python",
resolvePythonScriptPath("hello2.py"));
这种情况下要想获取错误信息需要修改代码如下:
public static void main(String[] args) throws IOException {
ProcessBuilder processBuilder = new ProcessBuilder("python",
resolvePythonScriptPath("hello2.py"));
Process process = processBuilder.start();
List<String> results = readProcessOutput(process.getInputStream());
List<String> errMsg = readProcessOutput(process.getErrorStream());
System.out.println("results = " + results);
System.out.println("errMsg = " + errMsg);
}
这样写是不是很繁琐,所以直接设置redirectErrorStream(true),世界瞬间清爽了。
使用Jython
Jython官网:https://www.jython.org
JSR-223脚本引擎
Java 6中首次引入的JSR-223定义了一组提供基本脚本功能的脚本API。这些方法提供了用于执行脚本以及在Java和脚本语言之间共享值的机制。该标准的主要目的是试图使Java与不同脚本语言具有互操性。
当然,我们可以为任何具有JVM实现的动态语言使用可插入脚本引擎架构。Jython是运行在JVM上的Python的Java平台实现。
在pom.xml中引入依赖
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>
使用下面的代码可以列出所有可用的脚本引擎:
public static void listEngines() {
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> engines = manager.getEngineFactories();
for (ScriptEngineFactory engine : engines) {
System.out.println("Engine name:" + engine.getEngineName());
System.out.println("Version: " + engine.getEngineVersion());
System.out.println("Language: " + engine.getLanguageName());
System.out.println("Short Names:");
for (String names : engine.getNames()) {
System.out.println(names);
}
}
}
如果我们看到控制台打印了下面相应的脚本引擎,则可以使用Jython
使用Jython脚本引擎的代码逻辑如下:
public static void main(String[] args) throws IOException, ScriptException {
StringWriter writer = new StringWriter();
ScriptContext context = new SimpleScriptContext();
context.setWriter(writer);
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("python");
engine.eval(new FileReader(resolvePythonScriptPath("hello.py")), context);
System.out.println("执行结果: " + writer.toString());
}
我们使用ScriptEngineManager类的getEngineByName方法为给定的短名称查找和创建ScriptEngine。在我们的例子中,我们可以传递python或jython,它们是与此引擎关联的两个短名称。、
执行结果
程序执行manager.getEngineByName("python")这句为null,解决方案有两种:
1、在调用getEngineByName之前,设置Options.importSite = false
简化代码如下:
import org.python.core.Options;
ScriptEngineManager manager = new ScriptEngineManager();
Options.importSite = false;
ScriptEngine engine = manager.getEngineByName("python");
2、使用官方提供的独立jar
<!--<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>-->
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.2</version>
</dependency>
执行成功
Python代码直接嵌入到Java代码
使用PythonInterpretor类可以将Python代码直接嵌入到Java代码中。
public static void main(String[] args) {
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
StringWriter output = new StringWriter();
pyInterp.setOut(output);
pyInterp.exec("print('Hello jython')");
System.out.println("执行结果: " + output.toString());
}
}
又遇到错误了。。。
两种解决方案:
1、错误信息最后一句给出了解决方案 ,设置 python.import.site=false
public static void main(String[] args) {
Properties props = new Properties();
props.put("python.import.site", "false");
PythonInterpreter.initialize(System.getProperties(), props, null);
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
StringWriter output = new StringWriter();
pyInterp.setOut(output);
pyInterp.exec("print('Hello jython')");
System.out.println("执行结果: " + output.toString());
}
}
2、使用官方提供的独立jar
<!--<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>-->
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.2</version>
</dependency>
运行成功
再看一个将两个数字加在一起的示例,使用get方法访问变量的值。
public static void main(String[] args) {
Properties props = new Properties();
props.put("python.import.site", "false");
PythonInterpreter.initialize(System.getProperties(), props, null);
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("x = 10 + 10");
PyObject x = pyInterp.get("x");
System.out.println("执行结果: " + x.asInt());
}
}
运行结果
Apache Commons Exec
我们还可以使用的另一个第三方库是Apache Common Exec,它试图克服Java Process API的一些缺陷。
在pom.xml中引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
public static void main(String[] args) throws IOException {
String line = "python " + resolvePythonScriptPath("hello.py");
CommandLine cmdLine = CommandLine.parse(line);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
int exitCode = executor.execute(cmdLine);
System.out.println("执行状态, 成功为0 , 非0为失败,结果为 = " + exitCode);
System.out.println("执行结果: " + outputStream.toString());
}
执行结果
使用HTTP进行操作
让我们回顾一下,与其尝试直接调用Python,不如考虑使用一个完善的协议(如HTTP)作为两种不同语言之间的抽象层。
实际上,Python附带了一个简单的内置HTTP服务器,我们可以使用共享内容或文件通过HTTP:
python -m http.server 9000
如果现在转到http://localhost:9000,我们将看到前面命令启动时所在目录的内容。
我们可以考虑使用其他一些流行的框架来创建更健壮的基于python的web服务或应用程序,它们是Flask和Django。
一旦我们有了可以访问的端点,我们就可以使用几个Java HTTP库中的任何一个来调用我们的Python web服务/应用程序实现。
GitHub完整源代码
https://github.com/wjchenge/javaCallPython
评论