1. Overview
1.概述
Python is an increasingly popular programming language, particularly in the scientific community due to its rich variety of numerical and statistical packages. Therefore, it’s not an uncommon requirement to able to invoke Python code from our Java applications.
Python是一种越来越流行的编程语言,特别是在科学界,由于其丰富的数字和统计软件包,它是一种非常受欢迎的语言。因此,能够从我们的Java应用程序中调用Python代码并不是一个罕见的要求。
In this tutorial, we’ll take a look at some of the most common ways of calling Python code from Java.
在本教程中,我们将看看从Java调用Python代码的一些最常见的方法。
2. A Simple Python Script
2.一个简单的Python脚本
Throughout this tutorial, we’ll use a very simple Python script which we’ll define in a dedicated file called hello.py:
在整个教程中,我们将使用一个非常简单的Python脚本,我们将在一个名为hello.py的专用文件中定义这个脚本。
print("Hello Baeldung Readers!!")
Assuming we have a working Python installation, when we run our script we should see the message printed:
假设我们有一个正常的Python安装,当我们运行我们的脚本时,我们应该看到打印的信息。
$ python hello.py
Hello Baeldung Readers!!
3. Core Java
3.Core Java[/strong
In this section, we’ll take a look at two different options we can use to invoke our Python script using core Java.
在这一节中,我们将看看两个不同的选项,我们可以用核心 Java 来调用我们的 Python 脚本。
3.1. Using ProcessBuilder
3.1.使用ProcessBuilder
Let’s first take a look at how we can use the ProcessBuilder API to create a native operating system process to launch python and execute our simple script:
让我们首先看看我们如何使用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);
}
In this first example, we’re running the python command with one argument which is the absolute path to our hello.py script. We can find it in our test/resources folder.
在第一个例子中,我们运行python命令,只有一个参数,就是hello.py脚本的绝对路径。我们可以在test/resources文件夹中找到它。
To summarize, we create our ProcessBuilder object passing the command and argument values to the constructor. It’s also important to mention the call to redirectErrorStream(true). In case of any errors, the error output will be merged with the standard output.
总而言之,我们创建了我们的ProcessBuilder对象,将命令和参数值传递给构造函数。同样重要的是要提到对redirectErrorStream(true)的调用。如果出现任何错误,错误输出将与标准输出合并。
This is useful as it means we can read any error messages from the corresponding output when we call the getInputStream() method on the Process object. If we don’t set this property to true, then we’ll need to read output from two separate streams, using the getInputStream() and the getErrorStream() methods.
这很有用,因为它意味着我们在调用Process对象的getInputStream()方法时,可以从相应的输出中读取任何错误信息。如果我们不把这个属性设置为true,那么我们就需要使用getInputStream()和getErrorStream()方法从两个不同的流中读取输出。
Now, we start the process using the start() method to get a Process object. Then we read the process output and verify the contents is what we expect.
现在,我们使用start()方法启动进程,得到一个Process对象。然后我们读取进程的输出,并验证其内容是我们所期望的。
As previously mentioned, we’ve made the assumption that the python command is available via the PATH variable.
如前所述,我们假设python命令可以通过PATH变量获得。
3.2. Working With the JSR-223 Scripting Engine
3.2.使用JSR-223脚本引擎
JSR-223, which was first introduced in Java 6, defines a set of scripting APIs that provide basic scripting functionality. These methods provide mechanisms for executing scripts and for sharing values between Java and a scripting language. The main objective of this standard was to try to bring some uniformity to interoperating with different scripting languages from Java.
JSR-223在Java 6中首次引入,定义了一组提供基本脚本功能的脚本API。这些方法提供了执行脚本以及在Java和脚本语言之间共享值的机制。该标准的主要目的是试图为从Java与不同脚本语言的互操作带来一些统一性。
We can use the pluggable script engine architecture for any dynamic language provided it has a JVM implementation, of course. Jython is the Java platform implementation of Python which runs on the JVM.
我们可以将可插拔的脚本引擎架构用于任何动态语言,当然,前提是它有JVM实现。Jython是Python的Java平台实现,在JVM上运行。。
Assuming that we have Jython on the CLASSPATH, the framework should automatically discover that we have the possibility of using this scripting engine and enable us to ask for the Python script engine directly.
假设我们在CLASSPATH上有Jython,框架应该自动发现我们有可能使用这个脚本引擎,并使我们能够直接询问Python脚本引擎。
Since Jython is available from Maven Central, we can just include it in our pom.xml:
由于Jython可以从Maven中心获得,我们只需在pom.xml中包含它。
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>
Likewise, it can also be downloaded and installed directly.
同样,它也可以下载并直接安装。
Let’s list out all the scripting engines that we have available to us:
让我们列举一下我们可以使用的所有脚本引擎。
ScriptEngineManagerUtils.listEngines();
If we have the possibility of using Jython, we should see the appropriate scripting engine displayed:
如果我们有可能使用Jython,我们应该看到相应的脚本引擎显示。
...
Engine name: jython
Version: 2.7.2
Language: python
Short Names:
python
jython
Now that we know we can use the Jython scripting engine, let’s go ahead and see how to call our hello.py script:
现在我们知道我们可以使用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());
}
As we can see, it is pretty simple to work with this API. First, we begin by setting up a ScriptContext which contains a StringWriter. This will be used to store the output from the script we want to invoke.
正如我们所看到的,使用这个API是非常简单的。首先,我们开始设置一个ScriptContext,其中包含一个StringWriter。这将被用来存储我们要调用的脚本的输出。
We then use the getEngineByName method of the ScriptEngineManager class to look up and create a ScriptEngine for a given short name. In our case, we can pass python or jython which are the two short names associated with this engine.
然后我们使用ScriptEngineManager类的getEngineByName方法,为给定的短名称查找并创建一个ScriptEngine。在我们的例子中,我们可以传递python或jython,这是两个与此引擎相关的短名称。
As before, the final step is to get the output from our script and check it matches what we were expecting.
和以前一样,最后一步是从我们的脚本中获得输出,并检查它是否与我们所期望的一致。
4. Jython
4.Jython
Continuing with Jython, we also have the possibility of embedding Python code directly into our Java code. We can do this using the PythonInterpretor class:
继续使用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());
}
}
Using the PythonInterpreter class allows us to execute a string of Python source code via the exec method. As before we use a StringWriter to capture the output from this execution.
使用PythonInterpreter类允许我们通过exec方法执行一串Python源代码。和以前一样,我们使用一个StringWriter来捕获这个执行的输出。
Now let’s see an example where we add two numbers together:
现在让我们看一个例子,我们把两个数字加在一起。
@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());
}
}
In this example we see how we can use the get method, to access the value of a variable.
在这个例子中,我们看到我们如何使用get方法,来访问一个变量的值。
In our final Jython example, we’ll see what happens when an error occurs:
在我们的最后一个Jython例子中,我们将看到错误发生时的情况。
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("import syds");
}
When we run this code a PyException is thrown and we’ll see the same error as if we were working with native Python:
当我们运行这段代码时,会抛出一个PyException,我们会看到和使用本地Python时一样的错误。
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: No module named syds
A few points we should note:
我们应该注意到几点。
- As PythonIntepreter implements AutoCloseable, it’s good practice to use try-with-resources when working with this class
- The PythonInterpreter class name does not imply that our Python code is interpreted. Python programs in Jython are run by the JVM and therefore compiled to Java bytecode before execution
- Although Jython is the Python implementation for Java, it may not contain all the same sub-packages as native Python
5. Apache Commons Exec
5.Apache Commons Exec
Another third-party library that we could consider using is Apache Common Exec which attempts to overcome some of the shortcomings of the Java Process API.
我们可以考虑使用的另一个第三方库是Apache Common Exec,它试图克服Java Process API的一些不足之处。
The commons-exec artifact is available from Maven Central:
commons-exec工件可从Maven Central获得。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
Now let’s how we can use this library:
现在让我们看看如何使用这个库。
@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());
}
This example is not too dissimilar to our first example using ProcessBuilder. We create a CommandLine object for our given command. Next, we set up a stream handler to use for capturing the output from our process before executing our command.
这个例子与我们使用ProcessBuilder的第一个例子没有太大区别。我们为我们给定的命令创建一个CommandLine对象。接下来,我们设置了一个流处理程序,用于在执行命令前从我们的进程中捕获输出。
To summarize, the main philosophy behind this library is to offer a process execution package aimed at supporting a wide range of operating systems through a consistent API.
总结起来,这个库的主要理念是提供一个进程执行包,旨在通过一致的API支持各种操作系统。
6. Utilizing HTTP for Interoperability
6.利用HTTP实现互操作性
Let’s take a step back for a moment and instead of trying to invoke Python directly consider using a well-established protocol like HTTP as an abstraction layer between the two different languages.
让我们退后一步,不要试图直接调用Python,而是考虑使用一个成熟的协议,如HTTP,作为两种不同语言之间的抽象层。
In actual fact Python ships with a simple built-in HTTP server which we can use for sharing content or files over HTTP:
实际上,Python带有一个简单的内置HTTP服务器,我们可以用它来通过HTTP共享内容或文件。
python -m http.server 9000
If we now go to http://localhost:9000, we’ll see the contents listed for the directory where we launched the previous command.
如果我们现在进入http://localhost:9000,我们会看到我们启动前一个命令的目录所列出的内容。
Some other popular frameworks we could consider using for creating more robust Python-based web services or applications are Flask and Django.
我们可以考虑使用其他一些流行的框架来创建更强大的基于Python的网络服务或应用程序,这些框架是Flask和Django。。
Once we have an endpoint we can access, we can use any one of several Java HTTP libraries to invoke our Python web service/application implementation.
一旦我们有了可以访问的端点,我们就可以使用几个Java HTTP库中的任何一个来调用我们的Python网络服务/应用程序实现。
7. Conclusion
7.结论
In this tutorial, we’ve learned about some of the most popular technologies for calling Python code from Java.
在本教程中,我们已经了解了从Java调用Python代码的一些最流行的技术。
As always, the full source code of the article is available over on GitHub.
一如既往,文章的完整源代码可在GitHub上获得。