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』ガイドを参照してください。