Come chiamare Python da Java

1. Panoramica

Python è un linguaggio di programmazione sempre più popolare, in particolare nella comunità scientifica grazie alla sua ricca varietà di pacchetti numerici e statistici. Pertanto, non è un requisito raro poter richiamare il codice Python dalle nostre applicazioni Java.

In questo tutorial, daremo uno sguardo ad alcuni dei modi più comuni per chiamare il codice Python da Java.

2. Un semplice script Python

Durante questo tutorial, utilizzeremo uno script Python molto semplice che definiremo in un file dedicato chiamato hello.py :

print("Hello Baeldung Readers!!")

Supponendo di avere un'installazione Python funzionante, quando eseguiamo il nostro script dovremmo vedere il messaggio stampato:

$ python hello.py Hello Baeldung Readers!!

3. Core Java

In questa sezione, daremo un'occhiata a due diverse opzioni che possiamo usare per invocare il nostro script Python usando Java core.

3.1. Utilizzando ProcessBuilder

Diamo prima un'occhiata a come possiamo utilizzare l' API ProcessBuilder per creare un processo del sistema operativo nativo per avviare Python ed eseguire il nostro semplice script:

@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); }

In questo primo esempio, stiamo eseguendo il comando python con un argomento che è il percorso assoluto del nostro script hello.py . Possiamo trovarlo nella nostra cartella test / risorse .

Per riassumere, creiamo il nostro oggetto ProcessBuilder passando i valori di comando e argomento al costruttore. È anche importante menzionare la chiamata a redirectErrorStream (true). In caso di errori, l'output dell'errore verrà unito allo standard output.

Ciò è utile in quanto significa che possiamo leggere qualsiasi messaggio di errore dall'output corrispondente quando chiamiamo il metodo getInputStream () sull'oggetto Process . Se non impostiamo questa proprietà su true , dovremo leggere l'output da due flussi separati, utilizzando i metodi getInputStream () e getErrorStream () .

Ora, iniziamo il processo utilizzando il metodo start () per ottenere un oggetto Process . Quindi leggiamo l'output del processo e verifichiamo che il contenuto sia quello che ci aspettiamo.

Come accennato in precedenza, abbiamo ipotizzato che il comando python sia disponibile tramite la variabile PATH .

3.2. Lavorare con il motore di scripting JSR-223

JSR-223, introdotto per la prima volta in Java 6, definisce un insieme di API di scripting che forniscono funzionalità di scripting di base. Questi metodi forniscono meccanismi per l'esecuzione di script e per la condivisione di valori tra Java e un linguaggio di scripting. L'obiettivo principale di questo standard era provare a portare una certa uniformità nell'interoperabilità con diversi linguaggi di scripting da Java.

Possiamo usare l'architettura del motore di script collegabile per qualsiasi linguaggio dinamico a condizione che abbia un'implementazione JVM, ovviamente. Jython è l'implementazione della piattaforma Java di Python che gira sulla JVM.

Supponendo di avere Jython su CLASSPATH , il framework dovrebbe scoprire automaticamente che abbiamo la possibilità di utilizzare questo motore di scripting e consentirci di richiedere direttamente il motore di script Python.

Poiché Jython è disponibile da Maven Central, possiamo semplicemente includerlo nel nostro pom.xml :

 org.python jython 2.7.2 

Allo stesso modo, può anche essere scaricato e installato direttamente.

Elenchiamo tutti i motori di scripting che abbiamo a nostra disposizione:

ScriptEngineManagerUtils.listEngines();

Se abbiamo la possibilità di utilizzare Jython, dovremmo vedere visualizzato il motore di scripting appropriato:

... Engine name: jython Version: 2.7.2 Language: python Short Names: python jython 

Ora che sappiamo che possiamo usare il motore di scripting Jython, andiamo avanti e vediamo come chiamare il nostro script 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()); }

Come possiamo vedere, è piuttosto semplice lavorare con questa API. Innanzitutto, iniziamo impostando uno ScriptContext che contiene uno StringWriter . Questo verrà utilizzato per memorizzare l'output dello script che vogliamo invocare.

Abbiamo quindi utilizzare il getEngineByName metodo della ScriptEngineManager di classe per cercare e creare uno ScriptEngine per un dato nome breve . Nel nostro caso, possiamo passare python o jython che sono i due nomi brevi associati a questo motore.

Come prima, il passaggio finale è ottenere l'output dal nostro script e verificare che corrisponda a ciò che ci aspettavamo.

4. Jython

Continuando con Jython, abbiamo anche la possibilità di incorporare codice Python direttamente nel nostro codice Java. Possiamo farlo usando la classe 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()); } }

L'utilizzo della classe PythonInterpreter ci consente di eseguire una stringa di codice sorgente Python tramite il metodo exec . Come prima usiamo uno StringWriter per catturare l'output di questa esecuzione.

Ora vediamo un esempio in cui aggiungiamo due numeri insieme:

@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 questo esempio vediamo come possiamo usare il metodo get , per accedere al valore di una variabile.

Nel nostro esempio finale di Jython, vedremo cosa succede quando si verifica un errore:

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

In questo tutorial, abbiamo appreso alcune delle tecnologie più popolari per chiamare il codice Python da Java.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.