Как да се обадя на Python от Java

1. Общ преглед

Python е все по-популярен език за програмиране, особено в научната общност, поради богатото си разнообразие от цифрови и статистически пакети. Следователно не е необичайно изискване да можете да извиквате Python код от нашите Java приложения.

В този урок ще разгледаме някои от най-често срещаните начини за извикване на Python код от Java.

2. Прост скрипт на Python

По време на този урок ще използваме много прост скрипт на Python, който ще определим в специален файл, наречен hello.py :

print("Hello Baeldung Readers!!")

Ако приемем, че имаме работеща инсталация на Python, когато стартираме нашия скрипт, трябва да видим съобщението отпечатано:

$ python hello.py Hello Baeldung Readers!!

3. Основна Java

В този раздел ще разгледаме две различни опции, които можем да използваме, за да извикаме нашия Python скрипт, използвайки основната Java.

3.1. Използване на ProcessBuilder

Нека първо разгледаме как можем да използваме API на ProcessBuilder, за да създадем собствен процес на операционна система за стартиране на 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). В случай на грешки изходът за грешка ще бъде обединен със стандартния изход.

Това е полезно, тъй като означава, че можем да прочетем всички съобщения за грешки от съответния изход, когато извикаме метода getInputStream () на обекта Process . Ако не зададем това свойство на true , тогава ще трябва да прочетем изхода от два отделни потока, използвайки методите getInputStream () и getErrorStream () .

Сега стартираме процеса, използвайки метода start () , за да получим обект Process . След това четем изхода на процеса и проверяваме съдържанието, което очакваме.

Както вече споменахме, направихме предположението, че командата python е достъпна чрез променливата PATH .

3.2. Работа с JSR-223 Scripting Engine

JSR-223, който беше представен за първи път в Java 6, дефинира набор от API за скриптове, които предоставят основна функционалност за скриптове. Тези методи предоставят механизми за изпълнение на скриптове и за споделяне на стойности между Java и скриптов език. Основната цел на този стандарт беше да се опита да внесе известна еднородност при взаимодействието с различни скриптови езици от Java.

Можем да използваме подвижната архитектура на скрипт двигателя за всеки динамичен език, при условие че има JVM реализация, разбира се. Jython е реализацията на Java платформа на Python, която работи на JVM.

Ако приемем, че разполагаме с Jython на CLASSPATH , рамката трябва автоматично да открие, че имаме възможността да използваме този скриптов механизъм и да ни даде възможност да поискаме директно скриптовия механизъм на 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 е доста проста. Първо, започваме с настройването на ScriptContext, който съдържа StringWriter . Това ще се използва за съхраняване на изхода от скрипта, който искаме да извикаме.

След това използвайте getEngineByName метода на ScriptEngineManager класа, за да търсите и да се създаде 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 класа ни позволява да изпълни поредица от Python изходния код чрез EXEC метод . Както и преди, използваме 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"); }

When we run this code a PyException is thrown and we'll see the same error as if we were working with native Python:

Traceback (most recent call last): File "", line 1, in  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

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.

The commons-exec artifact is available from Maven Central:

 org.apache.commons commons-exec 1.3 

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.

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.

6. Utilizing HTTP for Interoperability

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.

In actual fact Python ships with a simple built-in HTTP server which we can use for sharing content or files over HTTP:

python -m http.server 9000

If we now go to //localhost:9000, we'll see the contents listed for the directory where we launched the previous command.

Some other popular frameworks we could consider using for creating more robust Python-based web services or applications are Flask and 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.

7. Conclusion

В този урок научихме за някои от най-популярните технологии за извикване на Python код от Java.

Както винаги, пълният изходен код на статията е достъпен в GitHub.