参考:https://www.136.la/jingpin/show-185852.html
https://blog.csdn.net/Carlos_gloria/article/details/115625229
https://blog.csdn.net/qq_26591517/article/details/80441540
https://blog.csdn.net/it_xiao_bai/article/details/79074988
https://www.cnblogs.com/bethansy/p/7614749.html
https://blog.csdn.net/weixin_42414127/article/details/111410644
如何从 Java 调用 Python-3种方法
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 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 中:
org.python
jython
2.7.2
同样,也可以直接下载安装。
让我们列出所有可用的脚本引擎:
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,这是与这个引擎关联的两个短名称。
和以前一样,最后一步是从我们的脚本中获取输出并检查它是否符合我们的预期。
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 相同的所有子包
该公地EXEC神器可从Maven的中央:
org.apache.commons
commons-exec
1.3
现在让我们来看看如何使用这个库:
@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 支持各种操作系统。
service.java 编写调用脚本程序,具体见注释:
@Service
public class CallPythonServiceImpl implements CallPythonService {
@Value("${carlos.python.script.path}")
private String pythonScriptPath;
@Value("${carlos.python.path}")
private String pythonPath;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Override
public Object compute() throws IOException {
Map res = new HashMap<>();
// 解析数据库信息
String cleanUrl = url.substring(5);
URI uri = URI.create(cleanUrl);
String host = uri.getHost();
String database = uri.getPath().replace("/", “”);
// 传递数据库参数,便于python脚本中操作数据库,并与项目参数保持一致,也可以传递其他参数
String[] arg = new String[]{pythonPath, pythonScriptPath,
host, database, username, password};
// 打印出来的命令行,也是在bash或者cmd上执行的命令,可预先测试下
System.out.println("=command line=");
for (String s : arg) {
System.out.print(s + " “);
}
System.out.println(”\n=command line=");
Process proc;
BufferedReader in = null;
try {
proc = Runtime.getRuntime().exec(arg); // 执行py文件
in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
// 打印python脚本输出
System.out.println(line);
// 判断是否为返回结果
if (line.startsWith("{") && line.endsWith("}") && line.contains(“msg”) && line.contains(“status”)){
return JSON.parse(line);
}
}
in.close();
}catch (IOException e){
e.printStackTrace();
}finally {
if (in != null){
in.close();
}
}
res.put("status", "error");
res.put("msg", "python script run error");
return res;
}
}
编写调用脚本
from my_package import compute
import sys
host = sys.argv[1]
database = sys.argv[2]
username = sys.argv[3]
password = sys.argv[4]
if name == “main”:
compute(host, database, username, password)
注意这里使用的sys.argv来获取命令行参数,其中第一个参数为脚本地址,所以实际的参数是从第二个开始,索引为1。
本文coding地址:https://gitee.com/carlos_code/java-calls-python.git