1. 概述

Python 作为一门编程语言,近年来在科学计算、数据分析等领域越来越流行,尤其是得益于其丰富的数值计算和统计分析类库。因此,在 Java 应用中调用 Python 代码的需求也日益常见。

本文将系统性地介绍几种主流的 Java 调用 Python 的方式,涵盖从原生 API 到第三方库,再到服务化通信的多种方案。每种方式都有其适用场景和“坑”,我们一一拆解。


2. 一个简单的 Python 脚本

为统一示例,我们使用一个极简的 Python 脚本 hello.py,内容如下:

print("Hello Baeldung Readers!!")

假设你的系统已正确安装 Python,执行该脚本会输出:

$ python hello.py 
Hello Baeldung Readers!!

这个脚本将作为后续所有调用方式的测试目标。


3. 使用核心 Java API

Java 原生提供了进程控制能力,可以直接启动外部 Python 进程来执行脚本。这是最直接但也最“原始”的方式。

3.1. 使用 ProcessBuilder

ProcessBuilder 是 Java 中用于创建和管理外部进程的标准方式。我们可以用它来启动 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);
}

关键点说明:

  • redirectErrorStream(true):将标准输出和错误输出合并,避免需要同时读取两个流,简化处理逻辑。
  • resolvePythonScriptPath():用于获取脚本的绝对路径,确保进程能找到文件。
  • 踩坑提醒:该方式依赖系统环境变量 PATH 中能正确解析 python 命令。在不同环境(如 Windows/Linux)或虚拟环境(virtualenv)下容易出问题。
  • ⚠️ 若 Python 脚本有依赖包,需确保环境已安装,否则执行会失败。

这种方式简单粗暴,适合一次性脚本调用,但不适合高频率或复杂交互场景。


3.2. 使用 JSR-223 脚本引擎

JSR-223 是 Java 6 引入的脚本引擎规范,允许 Java 与动态语言(如 JavaScript、Groovy、Python)交互。

要支持 Python,我们需要 Jython —— 一个在 JVM 上运行的 Python 实现。

添加依赖

<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython-slim</artifactId>
    <version>2.7.3b1</version>
</dependency>

⚠️ 注意:Jython 目前仅支持 Python 2.7,不支持 Python 3.x,这是其最大限制。

查看可用脚本引擎

ScriptEngineManagerUtils.listEngines();

输出应包含:

Engine name: jython
Version: 2.7.2
Language: python
Short Names:
python
jython

执行 Python 脚本

@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());
}

优点:

  • 脚本在 JVM 内执行,无需启动外部进程,性能更好。
  • 支持 Java 与 Python 变量共享(通过 Bindings)。

缺点:

  • 仅支持 Python 2.7,无法使用现代 Python 生态。
  • 不支持 C 扩展(如 NumPy、Pandas),很多科学计算库无法使用。

4. Jython 进阶用法

Jython 提供了 PythonInterpreter 类,允许直接在 Java 中嵌入 Python 代码,适合轻量级逻辑嵌入。

4.1. 执行内联 Python 代码

@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());
    }
}

✅ 使用 try-with-resources 确保资源释放。

4.2. 变量传递与获取

@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());
    }
}

Java 可以通过 get() 获取 Python 变量,反之也可用 set() 传值。

4.3. 异常处理

try (PythonInterpreter pyInterp = new PythonInterpreter()) {
    pyInterp.exec("import syds");
}

会抛出 PyException,输出与原生 Python 一致:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named syds

⚠️ 重要提醒:

  • PythonInterpreter 名为“解释器”,实则在 JVM 上运行并编译为字节码。
  • Jython 并非完整 Python 实现,缺少大量原生模块和 C 扩展,不适合复杂科学计算场景

5. 使用 Apache Commons Exec

ProcessBuilder 虽然原生,但 API 较底层。Apache Commons Exec 提供了更高级的封装,简化进程管理。

添加依赖

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

执行 Python 脚本

@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());
}

优势:

  • 更简洁的流处理(PumpStreamHandler 自动泵送输出)。
  • 支持超时、监听器等高级功能。
  • 跨平台兼容性更好。

❌ 本质仍是启动外部进程,性能开销与 ProcessBuilder 类似。


6. 借助 HTTP 实现语言互通

与其在进程层面“硬刚”,不如换种思路:将 Python 服务化,通过 HTTP 接口调用。这才是生产环境推荐的做法。

6.1. 使用 Python 内置 HTTP 服务器

Python 自带简单 HTTP 服务:

python -m http.server 9000

访问 http://localhost:9000 即可浏览当前目录文件。

6.2. 使用 Web 框架暴露接口

更常见的做法是使用 FlaskDjango 构建 RESTful 接口。

示例 Flask 接口:

from flask import Flask
app = Flask(__name__)

@app.route("/hello")
def hello():
    return "Hello Baeldung Readers!!"

if __name__ == "__main__":
    app.run(port=9000)

6.3. Java 端调用接口

Java 可使用任意 HTTP 客户端(如 HttpClient、OkHttp、RestTemplate)调用:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:9000/hello"))
    .GET()
    .build();

HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
assertEquals("Hello Baeldung Readers!!", response.body());

这种方式的优势:

  • 解耦 Java 与 Python,各自独立部署、升级。
  • 易于监控、日志、限流、熔断。
  • 支持异步、批量、高并发调用。
  • 技术栈无关,未来可替换为 Go、Node.js 等。

❌ 缺点是引入网络开销和运维复杂度。


7. 总结

方式 适用场景 优点 缺点
ProcessBuilder 一次性脚本调用 原生支持,无需依赖 依赖环境,难调试
JSR-223 / Jython 轻量嵌入 Python 2.7 逻辑 JVM 内执行,性能好 不支持 Python 3,无 C 扩展
Apache Commons Exec 复杂进程管理 API 友好,功能丰富 仍是外部进程
HTTP 接口 生产环境推荐 解耦、可扩展、易维护 需额外服务

最终建议:

  • 小工具、脚本调用 → 用 ProcessBuilder 或 Commons Exec
  • 嵌入简单 Python 逻辑(且能接受 Python 2.7)→ Jython
  • 生产级应用 → 优先考虑 HTTP 服务化

源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/language-interop


原始标题:How to Call Python From Java | Baeldung