Jython移行ガイド

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

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

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

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

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

>>> 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とGraalPyの両方でまったく同じように機能します。ただし、GraalPyでは、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に似ていますが、GraalPyではより動的な照合方法が使用され、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アプリケーションに埋め込むことです。

JavaアプリケーションにJythonを埋め込むには、2つのオプションがあります。1つは、Jythonが提供するPythonInterpreterオブジェクトを使用することです。JavaコードにはJython内部クラスへの参照があるため、この方法でJythonを使用する既存のコードは(Maven構成などの)Jythonパッケージに直接依存します。これらのクラスはGraalVMに存在せず、同等のクラスは公開されません。この使用状況から移行するには、GraalVM SDKに切り替えます。このSDKを使用すると、Pythonに固有のAPIは公開されず、すべてがGraalVM APIを介して行われ、Pythonランタイムの最大構成が可能になります。

JavaにJythonを埋め込むもう1つのオプションは、JSR 223を介す方法で、javax.scriptパッケージのクラス、特にScriptEngineクラスを介して使用します。ScriptEngine APIはGraalPyのオプションおよび機能に完全には適合しないため、この方法はお薦めしません。ただし、既存のコードを移行するには、NetBeansプロジェクトでMaven Central上のパッケージが提供され、ここで役立ちます。Jythonを削除し、かわりに(例としてMavenのpom.xmlファイルを使用して)次の依存関係を追加します:

<dependency>
  <groupId>org.netbeans.api</groupId>
  <artifactId>org-netbeans-libs-graalsdk</artifactId>
  <version>RELEASE150</version> <!-- or any later release -->
</dependency>
<dependency>
  <groupId>org.netbeans.api</groupId>
  <artifactId>org-netbeans-api-scripting</artifactId>
  <version>RELEASE150</version> <!-- or any later release -->
</dependency>

その後、次を置き換えることでGraalPyの基本的な使用法を実現できます

ScriptEngine python = new ScriptEngineManager().getEngineByName("python");

置換後

import org.netbeans.api.scripting.Scripting;
// ...
ScriptEngineManager manager = Scripting.newBuilder().allowAllAccess(true).build();
ScriptEngine python = manager.getEngineByName("GraalVM:python");

これらのオプションはいずれも、アプリケーションがPython言語がインストールされているGraalVMで実行される場合にのみ機能することに注意することが重要です。詳細は、『Embed Languages』ガイドを参照してください。