如何从 Java 调用 Python-3种方法[spring boot等]

1. 概述

Python是一种越来越流行的编程语言,特别是在科学界,因为它具有丰富多样的数值和统计包。因此,能够从我们的 Java 应用程序调用 Python 代码的要求并不少见。

在本教程中,我们将了解一些从 Java 调用 Python 代码的最常见方法。

2. 一个简单的 Python 脚本

在本教程中,我们将使用一个非常简单的 Python 脚本,我们将在名为hello.py的专用文件中定义该脚本:

print("Hello Baeldung Readers!!")

假设我们有一个有效的 Python 安装,当我们运行我们的脚本时,我们应该看到打印的消息:

$ python hello.py 
Hello Baeldung Readers!!

3. 核心Java

在本节中,我们将看一下可用于使用核心 Java 调用 Python 脚本的两个不同选项。

3.1. 使用流程构建器
让我们首先看看我们如何使用ProcessBuilder API创建一个本地操作系统进程来启动python并执行我们的简单脚本:

@Test
public void givenPythonScript_whenPythonProcessInvoked_thenSuccess() throws Exception {
    ProcessBuilder processBuilder = new ProcessBuilder("python", resolvePythonScriptPath("hello.py"));
    processBuilder.redirectErrorStream(true);

    Process process = processBuilder.start();
    List<String> results = readProcessOutput(process.getInputStream());

    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain output of script: ", results, hasItem(
      containsString("Hello Baeldung Readers!!")));

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

在第一个示例中,我们使用一个参数运行python命令,该参数是hello.py脚本的绝对路径。我们可以在我们的test/resources文件夹中找到它。

总而言之,我们创建了ProcessBuilder对象,将命令和参数值传递给构造函数。提及对redirectErrorStream(true)的调用也很重要 。如果出现任何错误,错误输出将与标准输出合并。

这很有用,因为这意味着当我们在Process对象上调用getInputStream()方法时,我们可以从相应的输出中读取任何错误消息。如果我们不将此属性设置为true,那么我们将需要使用getInputStream()和getErrorStream()方法从两个单独的流中读取输出。

现在,我们使用start() 方法启动进程以获取 Process对象。然后我们读取流程输出并验证内容是否符合我们的预期。

如前所述,我们假设python 命令可通过 PATH变量使用。

3.2. 使用 JSR-223 脚本引擎

JSR-223首次在 Java 6 中引入,定义了一组提供基本脚本功能的脚本 API。这些方法提供了执行脚本和在 Java 和脚本语言之间共享值的机制。该标准的主要目标是尝试为与 Java 不同脚本语言的互操作带来一定的一致性。

我们可以将可插拔脚本引擎架构用于任何具有JVM实现的动态语言,当然。Jython是在 JVM 上运行的 Python 的 Java 平台实现。

假设我们在CLASSPATH上有 Jython ,框架应该会自动发现我们有可能使用这个脚本引擎,并使我们能够直接请求 Python 脚本引擎。

由于 Jython 可从Maven Central 获得,我们可以将它包含在我们的 pom.xml 中:

<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython</artifactId>
    <version>2.7.2</version>
</dependency>

同样,也可以直接下载安装。

让我们列出所有可用的脚本引擎:

ScriptEngineManagerUtils.listEngines();
如果我们有可能使用 Jython,我们应该会看到相应的脚本引擎显示出来:


Engine name: jython
Version: 2.7.2
Language: python
Short Names:
python
jython
现在我们知道我们可以使用 Jython 脚本引擎,让我们继续看看如何调用我们的hello.py脚本:

@Test
public void givenPythonScriptEngineIsAvailable_whenScriptInvoked_thenOutputDisplayed() throws Exception {
    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);
    assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", writer.toString().trim());
}

正如我们所见,使用这个 API 非常简单。首先,我们首先设置一个包含StringWriter的ScriptContext。这将用于存储我们要调用的脚本的输出。

然后,我们使用ScriptEngineManager类的getEngineByName方法为给定的短名称查找和创建ScriptEngine。在我们的例子中,我们可以传递python或jython,这是与这个引擎关联的两个短名称。

和以前一样,最后一步是从我们的脚本中获取输出并检查它是否符合我们的预期。

4. Jython

继续使用 Jython,我们还可以将 Python 代码直接嵌入到我们的 Java 代码中。我们可以使用PythonInterpretor类来做到这一点:

@Test
public void givenPythonInterpreter_whenPrintExecuted_thenOutputDisplayed() {
    try (PythonInterpreter pyInterp = new PythonInterpreter()) {
        StringWriter output = new StringWriter();
        pyInterp.setOut(output);

        pyInterp.exec("print('Hello Baeldung Readers!!')");
        assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", output.toString()
          .trim());
    }
}

使用PythonInterpreter类允许我们通过exec方法执行一串 Python 源代码。和之前一样,我们使用StringWriter来捕获这次执行的输出。

现在让我们看一个例子,我们将两个数字相加:

@Test
public void givenPythonInterpreter_whenNumbersAdded_thenOutputDisplayed() {
    try (PythonInterpreter pyInterp = new PythonInterpreter()) {
        pyInterp.exec("x = 10+10");
        PyObject x = pyInterp.get("x");
        assertEquals("x: ", 20, x.asInt());
    }
}

在这个例子中,我们看到了如何使用get方法来访问变量的值。

在我们最后的 Jython 示例中,我们将看到发生错误时会发生什么:

try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec(“import syds”);
}
当我们运行这段代码时,会抛出一个PyException异常,我们会看到与使用原生 Python 一样的错误:

Traceback (most recent call last):
File “”, line 1, in
ImportError: No module named syds
我们应该注意以下几点:

由于PythonIntepreter实现了AutoCloseable,因此在使用此类时最好使用try-with-resources
该PythonInterpreter类名称并不意味着我们的Python代码解释。Jython 中的 Python 程序由 JVM 运行,因此在执行前编译为 Java 字节码
尽管 Jython 是 Java 的 Python 实现,但它可能不包含与原生 Python 相同的所有子包

5. Apache Commons Exec

我们可以考虑使用的另一个第三方库是Apache Common Exec,它试图克服Java Process API 的一些缺点。

该公地EXEC神器可从Maven的中央:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>

现在让我们来看看如何使用这个库:

@Test
public void givenPythonScript_whenPythonProcessExecuted_thenSuccess() 
  throws ExecuteException, 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);
    assertEquals("No errors should be detected", 0, exitCode);
    assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", outputStream.toString()
      .trim());
}

这个例子与我们第一个使用ProcessBuilder 的例子并没有太大的不同。我们为给定的命令创建一个CommandLine对象。接下来,我们设置了一个流处理程序,用于在执行我们的命令之前捕获我们进程的输出。

总而言之,该库背后的主要理念是提供一个流程执行包,旨在通过一致的 API 支持各种操作系统。

6. 利用 HTTP 实现互操作性

让我们退后一步,与其尝试直接调用 Python,不如考虑使用一个完善的协议(如 HTTP)作为两种不同语言之间的抽象层。

实际上,Python 附带了一个简单的内置 HTTP 服务器,我们可以使用它来通过 HTTP 共享内容或文件:

python -m http.server 9000
如果我们现在转到http://localhost:9000,我们将看到为我们启动上一个命令的目录列出的内容。

我们可以考虑使用其他一些流行的框架来创建更强大的基于 Python 的 Web 服务或应用程序,例如Flask和Django。

一旦我们有了可以访问的端点,我们就可以使用多个Java HTTP库中的任何一个来调用我们的 Python Web 服务/应用程序实现。

7. 结论

在本教程中,我们了解了一些最流行的从 Java 调用 Python 代码的技术。

与往常一样,本文的完整源代码可 在 GitHub 上找到
翻译引用自

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值