杰瑞科技汇

python在java中调用python

下面我将详细介绍几种主流的实现方式,从最简单、最通用的到性能最高的,并附上完整的代码示例和优缺点分析。

python在java中调用python-图1
(图片来源网络,侵删)

核心思路

无论采用哪种技术,其核心思想都是相似的:

  1. Java 进程:你的 Java 应用程序作为主进程。
  2. 通信桥梁:通过某种机制(如标准输入/输出、Socket、共享内存等)让 Java 进程能够与 Python 解释器进行通信。
  3. Python 子进程:Java 启动一个 Python 解释器作为子进程,并让它执行指定的 Python 脚本。
  4. 数据交换:Java 将参数传递给 Python,Python 执行完毕后将结果(如字符串、数字、JSON)返回给 Java。

使用 Runtime.exec()ProcessBuilder (原生 Java)

这是最基础、最直接的方法,不依赖任何第三方库,Java 通过执行操作系统的命令来启动 Python 解释器。

工作原理

Java 使用 Runtime.getRuntime().exec()ProcessBuilder 类来执行一个命令行指令,python your_script.py arg1 arg2,Java 和 Python 子进程通过标准输入流和标准输出流进行通信。

代码示例

Python 脚本 (my_script.py)

python在java中调用python-图2
(图片来源网络,侵删)

这个脚本会从命令行参数中读取一个名字,计算一个斐波那契数,然后将结果以 JSON 格式输出到标准输出。

# my_script.py
import sys
import json
import time
# 从命令行参数获取输入
# sys.argv[0] 是脚本名,所以从 1 开始
if len(sys.argv) > 1:
    name = sys.argv[1]
    try:
        n = int(sys.argv[2])
    except (IndexError, ValueError):
        n = 10 # 默认值
else:
    name = "World"
    n = 10
# 模拟一个耗时操作
print(f"Python: Hello, {name}! Calculating Fibonacci for {n}...", file=sys.stderr)
time.sleep(2) # 模拟计算耗时
def fibonacci(num):
    a, b = 0, 1
    for _ in range(num):
        a, b = b, a + b
    return a
result = fibonacci(n)
# 将结果以 JSON 格式输出到标准输出,方便 Java 解析
output_data = {
    "greeting": f"Hello {name} from Python!",
    "fibonacci_result": result,
    "status": "success"
}
print(json.dumps(output_data))

Java 代码 (JavaPythonCaller.java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class JavaPythonCaller {
    public static void main(String[] args) {
        // Python 脚本的路径和参数
        String scriptPath = "path/to/your/my_script.py";
        String name = "Java User";
        int number = 15;
        // 构建命令
        // 注意:Windows 下 python 可能是 "python.exe",Linux/macOS 下是 "python3" 或 "python"
        String[] command = {
            "python", 
            scriptPath, 
            name, 
            String.valueOf(number)
        };
        try {
            // 使用 ProcessBuilder 启动进程,它比 Runtime.exec() 更灵活
            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true); // 将错误流合并到输出流,方便处理
            Process process = pb.start();
            // 读取 Python 脚本的输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line);
            }
            // 等待 Python 进程执行完毕
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("Java: Python script executed successfully.");
                System.out.println("Java: Received output from Python:");
                System.out.println(output.toString());
            } else {
                System.err.println("Java: Python script failed with exit code: " + exitCode);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

优点

  • 无依赖:不需要任何额外的 Java 库,是 Java 的标准功能。
  • 简单直接:概念简单,易于理解和实现。

缺点

  • 性能开销大:每次调用都需要创建一个新的 Python 进程,启动和销毁进程的开销很大。
  • 通信复杂:数据交换依赖于字符串(通过标准输入/输出),处理复杂数据结构(如列表、字典)非常麻烦,容易出错。
  • 错误处理困难:难以捕获 Python 脚本内部的详细错误信息。
  • 平台相关:Python 解释器的路径(python vs python3 vs python.exe)在不同操作系统上可能不同。

使用第三方库 (推荐)

为了解决原生方法的痛点,社区涌现出许多优秀的第三方库,它们封装了进程间的通信,提供了更高效、更便捷的 API。

Junrar

这是一个专门用于在 Java 中调用 Python 的库,它通过标准输入/输出与 Python 交互,但提供了非常友好的 API。

Maven 依赖:

<dependency>
    <groupId>org.junrar</groupId>
    <artifactId>junrar</artifactId>
    <version>7.5.3</version> <!-- 请使用最新版本 -->
</dependency>

注意junrar 这个名字可能引起误解,它最初是用于解压 RAR 文件的,但其作者后来扩展了功能来支持 Python 调用,请确保你添加的是正确的库。

代码示例:

import org.junrar.Python;
import org.junrar.PythonExecutionException;
import org.json.JSONObject;
public class JunrarExample {
    public static void main(String[] args) {
        try {
            // 创建 Python 执行器
            Python python = new Python();
            // 执行 Python 代码片段
            String pythonCode = "import json\n" +
                                "name = 'Junrar User'\n" +
                                "result = {'message': 'Hello from Junrar!', 'value': 42}\n" +
                                "print(json.dumps(result))";
            // 执行代码并获取输出
            String output = python.execute(pythonCode);
            // 解析 JSON 输出
            JSONObject resultJson = new JSONObject(output);
            System.out.println("Java: Received from Python: " + resultJson.getString("message"));
            System.out.println("Java: Value: " + resultJson.getInt("value"));
        } catch (PythonExecutionException e) {
            System.err.println("Python execution failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

优点

  • API 友好:比手动管理 Process 简单得多。
  • 易于使用:可以直接执行字符串形式的 Python 代码,也可以执行脚本文件。

缺点

  • 仍有进程开销:本质上还是在创建新的 Python 进程,性能瓶颈依然存在。
  • 依赖库:需要引入第三方库。

Pyrolite

这是一个更高级的库,它使用 Pyro (Python Remote Objects) 协议,它不通过标准输入/输出,而是通过 Socket 进行通信,性能远高于基于标准流的方法。

工作原理

  1. 一个 Python 脚本作为 Pyro 服务器启动,并暴露一些可调用的函数或对象。
  2. Java 客户端连接到这个 Pyro 服务器。
  3. Java 像调用本地方法一样直接调用 Python 端暴露的函数。
  4. 数据序列化是通过 pickle(Python)和 Pyrolite(Java)完成的,支持复杂对象。

Maven 依赖:

<dependency>
    <groupId>net.razorvine</groupId>
    <artifactId>pyrolite</artifactId>
    <version>5.13</version> <!-- 请使用最新版本 -->
</dependency>

Python 服务器端 (pyro_server.py)

import Pyro4
import time
# Pyro4 配置,使用更可靠的线程池
Pyro4.config.SERVERTYPE = "thread"
@Pyro4.expose
class PyroService(object):
    def hello(self, name):
        print(f"Python (Pyro): Received request from {name}")
        time.sleep(1) # 模拟耗时操作
        return f"Hello, {name}! This is a Pyro response."
# 启动一个名字服务器,让客户端可以找到我们的服务
daemon = Pyro4.Daemon()
ns = Pyro4.locateNS()
uri = daemon.register(PyroService)
ns.register("example.pyro_service",
分享:
扫描分享到社交APP
上一篇
下一篇