1. 概要

Python は、その豊富な数値および統計パッケージにより、特に科学界でますます人気のあるプログラミング言語です。 したがって、JavaアプリケーションからPythonコードを呼び出すことができることは珍しい要件ではありません。

このチュートリアルでは、JavaからPythonコードを呼び出す最も一般的な方法のいくつかを見ていきます。

2. シンプルなPythonスクリプト

このチュートリアル全体を通して、hello.pyという専用ファイルで定義する非常に単純なPythonスクリプトを使用します。

print("Hello Baeldung Readers!!")

Pythonが正常にインストールされていると仮定すると、スクリプトを実行すると、次のメッセージが出力されます。

$ python hello.py 
Hello Baeldung Readers!!

3. コアJava

このセクションでは、コアJavaを使用してPythonスクリプトを呼び出すために使用できる2つの異なるオプションを見ていきます。

3.1. ProcessBuilderを使用する

まず、 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);
}

この最初の例では、hello.pyスクリプトへの絶対パスである1つの引数を指定してpythonコマンドを実行しています。 test /resourcesフォルダーにあります。

要約すると、 ProcessBuilder オブジェクトを作成して、コマンドと引数の値をコンストラクターに渡します。 また、redirectErrorStream(true)の呼び出しについて言及することも重要です。 エラーが発生した場合、エラー出力は標準出力とマージされます。 

これは、 ProcessオブジェクトでgetInputStream()メソッドを呼び出すときに、対応する出力からエラーメッセージを読み取ることができることを意味するので便利です。 このプロパティをtrueに設定しない場合は、 getInputStream() getErrorStream()[を使用して、2つの別々のストリームから出力を読み取る必要があります。 X170X]メソッド。

ここで、 start()メソッドを使用してプロセスを開始し、Processオブジェクトを取得します。 次に、プロセス出力を読み取り、内容が期待どおりであることを確認します。

前述のように、pythonコマンドはPATH変数を介して使用できると想定しています。

3.2. JSR-223スクリプトエンジンの操作

JSR-223はJava6で最初に導入され、基本的なスクリプト機能を提供する一連のスクリプトAPIを定義します。 これらのメソッドは、スクリプトを実行し、Javaとスクリプト言語の間で値を共有するためのメカニズムを提供します。 この標準の主な目的は、Javaのさまざまなスクリプト言語との相互運用にある程度の統一性を持たせることでした。

もちろん、 JVM 実装があれば、任意の動的言語にプラグイン可能なスクリプトエンジンアーキテクチャを使用できます。 Jythonは、JVM上で実行されるPythonのJavaプラットフォーム実装です。

CLASSPATH にJythonがあるとすると、フレームワークは、このスクリプトエンジンを使用する可能性があることを自動的に検出し、Pythonスクリプトエンジンを直接要求できるようにする必要があります。

JythonはMavenCentral から入手できるため、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 Baeldung Readers!!", writer.toString().trim());
}

ご覧のとおり、このAPIを使用するのは非常に簡単です。 まず、StringWriterを含むScriptContextを設定することから始めます。 これは、呼び出したいスクリプトからの出力を格納するために使用されます。

次に、ScriptEngineManagerクラスのgetEngineByNameメソッドを使用して、指定された短い名前のScriptEngineを検索して作成します。 この場合、このエンジンに関連付けられている2つの短い名前である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クラスを使用すると、execメソッドを介してPythonソースコードの文字列を実行できます。 前と同じように、 StringWriter を使用して、この実行からの出力をキャプチャします。

次に、2つの数値を足し合わせた例を見てみましょう。

@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

注意すべきいくつかのポイント:

  • PythonIntepreterAutoCloseableを実装しているため、このクラスを操作するときはtry-with-resourcesを使用することをお勧めします
  • PythonInterpreter クラス名は、Pythonコードが解釈されることを意味するものではありません。 JythonのPythonプログラムはJVMによって実行されるため、実行前にJavaバイトコードにコンパイルされます
  • JythonはJavaのPython実装ですが、ネイティブPythonと同じサブパッケージがすべて含まれているとは限りません。

5. Apache Commons Exec

使用を検討できるもう1つのサードパーティライブラリは、 Apache Common Exec で、 Java ProcessAPIのいくつかの欠点を克服しようとします。

commons-exec アーティファクトは、 MavenCentralから入手できます。

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

この例は、ProcessBuilderを使用した最初の例とそれほど変わりません。 指定されたコマンドに対してCommandLineオブジェクトを作成します。 次に、コマンドを実行する前にプロセスからの出力をキャプチャするために使用するストリームハンドラーを設定します。

要約すると、このライブラリの背後にある主な哲学は、一貫性のあるAPIを介して幅広いオペレーティングシステムをサポートすることを目的としたプロセス実行パッケージを提供することです。

6. 相互運用性のためのHTTPの利用

少し前に戻って、Pythonを直接呼び出そうとする代わりに、2つの異なる言語間の抽象化レイヤーとしてHTTPなどの確立されたプロトコルを使用することを検討してみましょう。

実際、Pythonには、HTTPを介してコンテンツやファイルを共有するために使用できるシンプルな組み込みHTTPサーバーが付属しています。

python -m http.server 9000

http:// localhost:9000 に移動すると、前のコマンドを起動したディレクトリの内容が一覧表示されます。

より堅牢なPythonベースのWebサービスまたはアプリケーションを作成するために使用を検討できるその他の一般的なフレームワークには、FlaskおよびDjangoがあります。

アクセスできるエンドポイントができたら、いくつかの Java HTTP ライブラリのいずれかを使用して、PythonWebサービス/アプリケーションの実装を呼び出すことができます。

7. 結論

このチュートリアルでは、JavaからPythonコードを呼び出すための最も一般的なテクノロジーのいくつかについて学びました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。