Moving from Jython to GraalVM

原文はこちら。
The original article was written by Tim Felgentreff (the language lead for the Python implementation on GraalVM, Oracle Labs).
https://medium.com/graalvm/moving-from-jython-to-graalvm-cf52c4af6106

Jythonは常にPythonとJavaの使いやすい統合を目指してきました。Jythonは、1997年にJim Huguninが “JPython” として開発を開始し、以後開発が続けられてきましたが、JythonはPython 2.7系のままで、2020年の初めからもはやメンテナンスされていません。

GraalVMには、Python 3実装が付属しています。これは継続的に改善されており、GraalVM APIを介して既存のJavaアプリケーションと簡単に統合できます。以下はその例です。

>>> import java.awt as awt
>>> win = awt.Frame()
>>> win.setSize(200, 200)
>>> win.setTitle("Hello from Python!")
>>> win.getSize().toString()'java.awt.Dimension[width=200,height=200]'
>>> win.show()

この例は、JythonとGraalVM上のPythonの両方でまったく同じように動作します。このようにJavaオブジェクトと直接対話できることは、生産性にとって非常に役立ちます。それは多くのJythonユーザーがする通りです。GraalVM上のPythonでも同じことが可能です。簡単に置き換えできるものではありませんが、GraalVM上のPythonは、JythonアプリケーションをPython 3に移行するための最も簡単な移行パスを提供すると考えています。

それでは、Jythonのユースケースと、GraalVMでどのように支援できるかを見ていきましょう。

Using Java from Python

(注意)このサンプルはGraalVM PythonのJython互換モードを使う必要があります。以下のように、–python.EmulateJythonoption を付けることで有効になります。

$ graalpython --jvm --experimental-options --python.EmulateJython

Importing

PythonからJavaクラスを簡単にインポートできることはすでにご存知の通りです。Import文を使ってJavaクラスをインポートできますが、Jythonとは異なり、java名前空間のパッケージのみを直接インポートできます。そのため、以下の例は動作します。

import java.lang as lang

以下の例は動作しません。

import javax.swing as swing
from javax.swing import *

代わりに、関心のあるクラスの1つを直接インポートする必要があります。

import javax.swing.Window as Window

Basic Object Usage

Javaオブジェクトやクラスの構築や作業は、自然なPythonの構文で行われます。クラスを関数として呼び出すことでクラスを構築し、クラスのメソッドを期待通りに呼び出すことができます。Javaオブジェクトのメソッドは、Pythonのメソッドと同じように、(インスタンスにバインドされている)ファーストクラスのオブジェクトとして取得したり渡したりすることもできます。

>>> from java.util import Random
>>> rg = Random(99)
>>> boundNextInt = rg.nextInt
>>> rg.nextInt()
1491444859
>>> boundNextInt()
1672896916

Java-to-Python Types: Automatic Conversion

メソッドのオーバーロードは、Pythonの引数をベストエフォートな方法で利用可能なパラメータ型にマッチングさせることで解決されます。このタイミングでデータ変換も行われます。ここでの目的は、可能な限りスムーズにPythonからJavaを使用することです。ここで行うマッチングはJythonに似ていますが、GraalVM Pythonでは、よりダイナミックなアプローチでマッチングを行っています。intやfloatをエミュレートするPythonの型も適切なJavaの型に変換します。要素がこれらのJavaプリミティブ型に適合している場合には、これにより、例えばdouble[][]としてPandaフレームを使ったり、int としてNumPy配列要素を使うことができます。

Automatic conversion of Java types to Python

(注意)

Python 3はPython 2といくつかの点で大きく異なっており、この違いは型の自動変換にも現れています。例えば、byte[]はstrではなくbyte型にマップされ、long型は存在しません。

Special Jython Modules

私たちは特別なJythonモジュールを提供していません。それらの機能のほとんどは、GraalVM PythonのJava APIを使っても同様に表現できると考えています。例えば、Jythonのjarrayモジュールでは、Javaのプリミティブな型の配列を構築できます。これはGraalVM Python上では以下のように実現できます。

>>> import java
>>> java.type("int[]")(10)

自動変換を考えると、Java配列を渡すことが必要なコードもまたPythonの型を利用できますが、暗黙のうちにこれは配列データのコピーを必要としていることがあり、出力パラメータとしてJava配列を使用する場合にはあてにならないことがあります。

>>> i = java.io.ByteArrayInputStream(b"foobar")
>>> buf = [0, 0, 0]
>>> i.read(buf) # buf is automatically copied into to a temporary byte[] array
3
>>> buf
[0, 0, 0] # the temporary byte[] array got lost
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]

Exceptions from Java

JythonはJavaの例外をPythonに自動的にマッピングして、except句で使用します。しかしこれはパフォーマンスの低下を伴うため、GraalVMのPythonでは –python.EmulateJython フラグを付けた場合にのみ有効になります。このフラグを指定すると、Jythonと同じように動作します(Python 3構文でのexcept句の場合のみ)。

>>> import java
>>> v = java.util.Vector()
>>> try:
...    x = v.elementAt(7)
... except java.lang.ArrayIndexOutOfBoundsException as e:
...    print(e.getMessage())
...
7 >= 0

Java Collections

Jythonとは異なり、辞書要素にアクセスするためのPythonの構文を自動的にjava.util.MapやListクラスのget、set、putメソッドにマッピングすることはありません。つまり、これらのMapクラスやListクラスを使用するには、Javaのメソッドを呼び出す必要があります。

>>> ht = java.util.Hashtable()
>>> ht.put("foo", "bar")
>>> ht.get("foo")
'bar'

同様に、Javaのjava.util.Enumerable、java.util.Iterator、java.util.IterableのPythonスタイルの反復処理も(現時点では)サポートしていません。これらの場合は、while ループを使用して hasNext() メソッドと next() メソッドを使用する必要があります。現在これらの不都合を解消するために取り組んでいます。

(Not) Inheriting from Java

継承(Inheritance)はJavaフレームワークで広く使われており、JythonではPythonクラスがJavaクラスから継承できるようにすることで、このユースケースをサポートしていますが、GraalVMのPythonではそうではありません。その理由の一つには、GraalVM Native Imageが関係しています。実行時に実行されるすべてのバイトコードのclosed-world解析を行えるようにしたいのですが、Jythonのようにバイトコードを動的に作成してそれをロードしてサブクラスを生成することはできません。

回避策としては、柔軟なサブクラスを作成、コンパイルの上、委譲(delegation)を代わりに使うというものです。

package my.python.logging;
import java.util.logging.Handler;

public class PythonHandler extends Handler {
    private final Value pythonDelegate;
    public PythonHandler(Value pythonDelegate) {
        this.pythonDelegate = pythonDelegate;
    }
    public void publish(LogRecord record) {
        pythonDelegate.invokeMember("publish", record);
    }
    public void flush() {
        pythonDelegate.invokeMember("flush");
    }
    public void close() {
        pythonDelegate.invokeMember("close");
    }
}

Pythonでは以下のように利用できます。

from java.util.logging import LogManager, Logger
from my.python.logging import PythonHandler
class MyHandler():
    def publish(self, logRecord):
        print("[python]",     logRecord.toString())
    def flush(): pass
    def close(): pass
LogManager.getLogManager().addLogger(Logger('my.python.logger', None, PythonHandler(MyHandler())))

Embedding Python into Java

Jythonを使用するもう一つの方法は、Javaアプリケーションに埋め込むことです。上記では、GraalVM Pythonが既存のJythonコードとの互換性の何らかの手段を提供していましたが、このケースでは何も提供しません。Jythonを使用する既存のコードは、JavaコードがPythonInterpreterなどのJython内部クラスへの参照を持っているため、(例えば、Mavenの設定で)Jythonパッケージに直接依存します。対してGraalVM Pythonの場合は、GraalVM SDK以外の依存関係は必要ありません。

Graal SDK (maven repository)
https://mvnrepository.com/artifact/org.graalvm.sdk/graal-sdk

Python特有のAPIが公開されているわけではなく、すべてGraalVM APIを使います。ここで重要なのは、Python言語がインストールされた状態でGraalVM上でアプリケーションを実行する限り、プログラムにPythonを埋め込むことができるということです。詳細については、埋め込みに関するドキュメントを参照してください。

Embed Languages with the GraalVM Polyglot API
https://www.graalvm.org/docs/reference-manual/embed/#Function_Python

Conclusion

JythonからGraalVMへの移行には、細部に注意を払い、両者の違いを理解する必要があります。Python 2からPython 3への移行だけでも、それ自体は必ずしも些細なことではありませんし、Jythonとの完全な互換性を提供できるとは限りません。

しかし、GraalVMでPythonをPython 3に忠実なままにすることは、長期的にはアプリケーション(とコミュニティ)に利益をもたらすでしょう。–python.EmulateJython というコマンドラインオプションで互換性モードを有効にすることで、既存のJythonアプリケーションをGraalVM上のPythonにできるだけ簡単に適応させることができるという、Jythonからの最も簡単な移行パスを提供しようとしています。

簡単に置き換え可能な互換性はありませんが、新しいバージョンのPython、より高速なGraalVMランタイムの利点、Jythonがサポートしていなかったネイティブ拡張機能のサポート、GraalVM Native Imageを使ったバイナリ作成の可能性など、GraalVM PythonはJythonアプリケーションのための説得力のある移行パスを提供しています。私たちはJythonの内部ユーザーを見てきましたが、みなさまのユースケースを伺いたいと思っています。JythonからGraalVMに移行するために必要なものを教えてください。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中