Jython移行ガイド

Java統合を使用するほとんどのJythonコードは、安定したJythonリリースに基づくものと考えられ、これらはPython 2.xバージョンでのみ機能します。一方、GraalVMのPythonランタイムはPython 3.xのみをターゲットにしています。GraalVMは、このような以前の2.xバージョンのJythonとの完全な互換性を備えていません。したがって、すべてのコードをPython 3に移行するには、重要な移行ステップを実行する必要があります。

Jython固有の機能については、このドキュメントに従ってGraalVMのPythonランタイムへの移行について学習してください。

Jythonの一部の機能はランタイム・パフォーマンスに悪影響を及ぼすため、デフォルトでは無効になっています。移行を容易にするために、GraalVMでコマンドライン・フラグ--python.EmulateJythonを使用して一部の機能を有効にできます。

Javaパッケージのインポート

JythonのJava統合には、GraalVMのPythonランタイムでデフォルトで有効になっている特定の機能があります。例:

>>> 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()

この例は、GraalVM上のJythonとPythonの両方でまったく同じように機能します。ただし、GraalVMでは、javaネームスペース内のパッケージのみを直接インポートできます。javaネームスペース外のパッケージからもクラスをインポートするには、--python.EmulateJythonオプションを有効にする必要があります。

また、JavaパッケージをPythonモジュールとしてインポートすることは、非常に特殊な状況でのみサポートされます。たとえば、次のものは機能します:

import java.lang as lang

次のものは機能しません:

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

かわりに、目的のクラスのいずれかを直接インポートする必要があります:

import javax.swing.Window as Window

基本的なオブジェクトの使用方法

Javaオブジェクトおよびクラスの構築と操作は、自然なPython構文を使用して行われます。また、Pythonメソッドと同様に、Javaオブジェクトのメソッドを(インスタンスにバインドされた)最初のクラス・オブジェクトとして取得したり、渡すこともできます:

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

Java型とPython型: 自動変換

メソッドのオーバーロードは、ベストエフォート方式でPython引数を使用可能なパラメータ型と照合することによって解決されます。このことは、データ変換中にも行われます。ここでの目標は、Pythonからできるかぎり円滑にJavaを使用できるようにすることです。ここで許可されている照合はJythonに似ていますが、GraalVMのPythonランタイムではより動的な照合方法が使用され、intまたはfloatをエミュレートするPython型も適切なJava型に変換されます。これにより、たとえば、Pandasフレームをdouble[][]として使用したり、NumPy配列要素をint[]として使用できます(これらの要素がそれらのJavaプリミティブ型に適合する場合)。

Java型 Python型
null なし
boolean bool
byte、short、int、long int、__int__メソッドを持つ任意のオブジェクト
float float、__float__メソッドを持つ任意のオブジェクト
char 長さ1のstr
java.lang.String str
byte[] bytes、bytearray、適切な型のみを含むラップされたJava配列またはPythonリスト
Java配列 適切な型のみを含むラップされたJava配列またはPythonリスト
Javaオブジェクト 適切な型のラップされたJavaオブジェクト
java.lang.Object 任意のオブジェクト

特殊なJythonモジュール

プリミティブJava配列の作成に使用されるjarrayモジュールは、互換性のためにサポートされています。

>>> import jarray
>>> jarray.array([1,2,3], 'i')

この使用方法は、java.type関数を使用して配列型を構築し、配列に値を設定することと同じであることに注意してください。

>>> 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 converted to a byte[] array
3
>>> buf
[0, 0, 0] # the converted byte[] array got lost
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]

jarray以外のモジュールはサポートされていません。

Javaからの例外

すべての種類のJava例外を捕捉することはパフォーマンス・ペナルティを伴い、--python.EmulateJythonオプションでのみ有効になります。

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

Javaコレクション

java.util.Collectionを実装するJava配列およびコレクションには、[]構文を使用してアクセスできます。空のコレクションはブール変換ではfalseとみなされます。これらの長さは、len組込み関数によって公開されます。

>>> from java.util import ArrayList
>>> l = ArrayList()
>>> l.add("foo")
True
>>> l.add("baz")
True
>>> l[0]
'foo'
>>> l[1] = "bar"
>>> del l[1]
>>> len(l)
1
>>> bool(l)
True
>>> del l[0]
>>> bool(l)
False

java.lang.Iterableを実装するJava反復可能オブジェクトは、forループまたはiter組込み関数を使用して反復できます。また、反復可能オブジェクトを予期するすべての組込み関数に指定できます。

>>> [x for x in l]
['foo', 'bar']
>>> i = iter(l)
>>> next(i)
'foo'
>>> next(i)
'bar'
>>> next(i)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> set(l)
{'foo', 'bar'}

イテレータも反復することができます。

>>> from java.util import ArrayList
>>> l = ArrayList()
>>> l.add("foo")
True
>>> i = l.iterator()  # Calls the Java iterator methods
>>> next(i)
'foo'

java.util.Mapを実装するマップ・コレクションには、[]表記を使用してアクセスできます。空のマップはブール変換ではfalseとみなされます。マップの反復によって、dictと一致するキーが生成されます。

>>> from java.util import HashMap
>>> m = HashMap()
>>> m['foo'] = 5
>>> m['foo']
5
>>> m['bar']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: bar
>>> [k for k in m]
['foo']
>>> bool(m)
True
>>> del m['foo']
>>> bool(m)
False

Javaからの継承

Javaクラスからの継承またはインタフェースの実装はサポートされますが、Jythonとは構文の違いがいくつかあります。Javaクラスから継承するクラスは、通常のclass文を使用して作成できます。ここで宣言されるメソッドは、名前が一致したときにスーパークラス・メソッドをオーバーライド/実装します。スーパー・コールは、特別な属性self.__super__を使用して実行されます。作成されたオブジェクトはPythonオブジェクトのようには動作しませんが、外部Javaオブジェクトのように動作します。Pythonレベルのメンバーには、そのthis属性を使用してアクセスできます。例:

import atexit
from java.util.logging import Logger, Handler


class MyHandler(Handler):
    def __init__(self):
        self.logged = []

    def publish(self, record):
        self.logged.append(record)


logger = Logger.getLogger("mylog")
logger.setUseParentHandlers(False)
handler = MyHandler()
logger.addHandler(handler)
# Make sure the handler is not used after the Python context has been closed
atexit.register(lambda: logger.removeHandler(handler))

logger.info("Hi")
logger.warning("Bye")

# The python attributes/methods of the object have to be accessed through 'this' attribute
for record in handler.this.logged:
    print(f'Python captured message "{record.getMessage()}" at level {record.getLevel().getName()}')

JavaへのPythonの埋込み

Jythonを使用する別の方法は、それをJavaアプリケーションに埋め込むことです。前述のGraalVMのPythonランタイムが既存のJythonコードとの互換性をある程度備えていた場合、このケースでは何も提供されません。JavaコードにはPythonInterpreterなどのJython内部クラスへの参照があるため、Jythonを使用する既存のコードは(Maven構成などの) Jythonパッケージに直接依存します。

GraalVMのPythonランタイムについては、GraalVM SDK以外に対する依存性は必要ありません。公開されている、Pythonに固有のAPIはなく、すべてがGraalVM APIを介して実行されます。

Python言語がインストールされたGraalVMでアプリケーションが実行されているかぎり、Pythonをプログラムに埋め込むことができる点に注意することが重要です。詳細は、『Embed Languages』ガイドを参照してください。