Перейти к основному содержимому

Как вызвать Python из Java

· 7 мин. чтения

1. Обзор

Python становится все более популярным языком программирования, особенно в научном сообществе, благодаря большому разнообразию числовых и статистических пакетов. Таким образом, возможность вызывать код Python из наших Java-приложений — обычное дело.

В этом руководстве мы рассмотрим некоторые из наиболее распространенных способов вызова кода Python из Java.

2. Простой скрипт Python

В этом руководстве мы будем использовать очень простой скрипт Python, который мы определим в специальном файле с именем hello.py :

print("Hello ForEach Readers!!")

Предполагая, что у нас есть рабочая установка Python, когда мы запускаем наш скрипт, мы должны увидеть напечатанное сообщение:

$ python hello.py 
Hello ForEach 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<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 ForEach 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

JSR-223 , впервые представленный в Java 6, определяет набор API-интерфейсов сценариев, обеспечивающих базовые функции сценариев. Эти методы предоставляют механизмы для выполнения сценариев и обмена значениями между Java и языком сценариев. Основная цель этого стандарта состояла в том, чтобы попытаться внести некоторое единообразие во взаимодействие с различными языками сценариев из Java.

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

Предполагая, что у нас есть Jython в CLASSPATH , платформа должна автоматически обнаружить, что у нас есть возможность использовать этот механизм сценариев, и позволить нам напрямую запрашивать механизм сценариев Python.

Поскольку Jython доступен в Maven Central , мы можем просто включить его в наш pom.xml :

<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>

Кроме того, его также можно загрузить и установить напрямую.

Давайте перечислим все доступные нам скриптовые движки:

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 ForEach Readers!!", writer.toString().trim());
}

Как видим, работать с этим API довольно просто. Во- первых, мы начнем с настройки ScriptContext , который содержит StringWriter . Это будет использоваться для хранения вывода скрипта, который мы хотим вызвать.

Затем мы используем метод getEngineByName класса ScriptEngineManager для поиска и создания ScriptEngine для заданного короткого имени . В нашем случае мы можем передать python или jython — два коротких имени, связанных с этим движком.

Как и прежде, последний шаг — получить вывод из нашего скрипта и проверить, соответствует ли он тому, что мы ожидали.

4. Джейтон

Продолжая 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 ForEach Readers!!')");
assertEquals("Should contain script output: ", "Hello ForEach 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");
}

Когда мы запускаем этот код, возникает PyException , и мы видим ту же ошибку, как если бы мы работали с родным Python:

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

Несколько моментов, которые мы должны отметить:

  • Поскольку PythonIntepreter реализует AutoCloseable , рекомендуется использовать try-with-resources при работе с этим классом .
  • Имя класса PythonInterpreter не означает, что наш код Python интерпретируется. Программы Python в Jython запускаются JVM и поэтому перед выполнением компилируются в байт-код Java.
  • Хотя Jython является реализацией Python для Java, он может не содержать все те же подпакеты, что и нативный Python.

5. Исполнительный директор Apache Commons

Еще одна сторонняя библиотека, которую мы могли бы использовать, — это Apache Common Exec , которая пытается преодолеть некоторые недостатки Java Process API .

Артефакт commons-exec доступен в Maven Central :

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

Теперь давайте посмотрим, как мы можем использовать эту библиотеку:

@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 ForEach Readers!!", outputStream.toString()
.trim());
}

Этот пример не слишком отличается от нашего первого примера с использованием ProcessBuilder . Мы создаем объект CommandLine для данной команды. Затем мы настраиваем обработчик потока, который будет использоваться для захвата вывода нашего процесса перед выполнением нашей команды.

Подводя итог, можно сказать, что основной философией этой библиотеки является предложение пакета выполнения процессов, предназначенного для поддержки широкого спектра операционных систем через согласованный API.

6. Использование HTTP для взаимодействия

Давайте сделаем шаг назад и вместо того, чтобы пытаться вызывать Python напрямую, рассмотрим использование хорошо зарекомендовавшего себя протокола, такого как HTTP, в качестве уровня абстракции между двумя разными языками.

На самом деле Python поставляется с простым встроенным HTTP-сервером, который мы можем использовать для обмена контентом или файлами через HTTP :

python -m http.server 9000

Если мы сейчас перейдем к http://localhost:9000 , мы увидим содержимое, указанное для каталога, в котором мы запустили предыдущую команду.

Некоторые другие популярные фреймворки, которые мы могли бы использовать для создания более надежных веб-сервисов или приложений на основе Python, — это Flask и Django .

Когда у нас есть конечная точка, к которой мы можем получить доступ, мы можем использовать любую из нескольких библиотек Java HTTP для вызова нашей реализации веб-службы/приложения Python.

7. Заключение

В этом руководстве мы узнали о некоторых наиболее популярных технологиях вызова кода Python из Java.

Как всегда, полный исходный код статьи доступен на GitHub .