JVM向け最新Python

Pythonバージョン2(現在はEOL)の場合、JythonはPythonとJavaを連携させるためのデファクトスタンダードです。既存のJythonコードのほとんどは、Java統合を使用しており、安定したJythonリリースに基づいています。しかし、これらはPython 2.xバージョンでのみ利用可能です。一方、GraalPyはPython 3.xと互換性があり、以前の2.xバージョンのJythonとの完全な互換性はありません。

Python 2からPython 3にコードを移行するには、Pythonコミュニティの公式ガイドに従ってください。JythonコードがPython 3と互換になったら、このガイドに従ってGraalPyとJythonのその他の違いを解消してください。

GraalPyのJava相互運用性のファーストクラスサポートにより、PythonからJavaライブラリを使用することが、他のGraal言語(Truffleフレームワーク上に実装された言語)の汎用的な相互運用性サポートを超えるJavaコードのための特別な機能により、可能な限り容易になります。

Jythonのすべての機能がGraalPyでサポートされているわけではありません。一部の機能はサポートされていますが、ランタイムパフォーマンスへの悪影響のため、デフォルトでは無効になっています。移行中に、コマンドラインオプション `--python.EmulateJython` を使用してこれらの機能を有効にすることができます。ただし、最適なパフォーマンスを実現するには、これらの機能を使用しないことをお勧めします。

Jythonスクリプトの移行 #

プレーンなJythonスクリプトをJythonからGraalPyに移動するには、GraalPy JVMベースのランタイムを使用します。(詳細については、利用可能なGraalPyディストリビューションを参照してください)。

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

GraalPyでは、Jythonの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とGraalPyの両方で実行した場合に同じ結果になります。ただし、この例をGraalPyで実行する場合、`java`名前空間にあるパッケージのみを直接インポートできます。`java`名前空間以外のパッケージからクラスをインポートするには、`--python.EmulateJython`オプションを使用します。

注:GraalPyをモジュール化されたアプリケーションに埋め込む場合、JSR 376に従って必要なモジュールにエクスポートを追加する必要がある場合があります。

さらに、すべての状況でJavaパッケージをPythonモジュールとしてインポートすることはできません。たとえば、これは動作します

import java.lang as lang

しかし、これは動作しません

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

代わりに、クラスの1つを直接インポートします

import javax.swing.Window as Window

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

Javaオブジェクトとクラスの構築と操作は、従来のPython構文を使用して実現されます。 Javaオブジェクトのメソッドは、Pythonメソッドと同じ方法で、ファーストクラスオブジェクト(インスタンスにバインド)として取得および参照することもできます。 例えば

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

JavaからPythonへの型:自動変換 #

メソッドのオーバーロードは、Pythonの引数を可能な限りパラメータ型に一致させることで解決されます。 このアプローチは、データを変換する場合にも採用されます。 ここでの目標は、PythonからJavaを使用することを可能な限りスムーズにすることです。 GraalPyが採用しているマッチングアプローチはJythonに似ていますが、GraalPyはより動的なマッチングアプローチを使用しています。`int`または`float`をエミュレートするPython型も適切なJava型に変換されます。 これにより、たとえば、要素がJavaプリミティブ型に収まる場合、Pandasフレームを`double[][]`として、またはNumPy配列要素を`int[]`として使用できます。

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

特別なJythonモジュール:`jarray` #

GraalPyは、互換性のために`jarray`モジュール(プリミティブJava配列を作成するため)を実装しています。 例えば

>>> 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 is lost
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]

Javaからの例外 #

Java例外をキャッチするには、`--python.EmulateJython`オプションを使用します。

注:Java例外をキャッチすると、パフォーマンスが低下します。

例えば

>>> 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クラスからの継承(またはJavaインターフェースの実装)は、Jythonとは構文上の違いがいくつかありますが、サポートされています。 Javaクラスから継承する(またはJavaインターフェースを実装する)クラスを作成するには、従来のPython `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 are 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アプリケーションに埋め込むことでした。 このような埋め込みには、2つのオプションがありました。

  1. Jythonが提供する`PythonInterpreter`オブジェクトを使用します。 この方法でJythonを使用する既存のコードは、JavaコードにJython内部クラスへの参照があるため、Jythonパッケージに直接依存します(たとえば、Maven構成で)。 これらのクラスはGraalVMには存在せず、同等のクラスも公開されていません。 この使用法から移行するには、GraalVM SDKに切り替えます。 このSDKを使用すると、Pythonに固有のAPIは公開されず、すべてがGraalVM APIを介して実現され、Pythonランタイムの構成可能性が最大になります。 セットアップの準備については、はじめにドキュメントを参照してください。

  2. `javax.script`パッケージのクラス、特に`ScriptEngine`クラスを使用して、JSR 223を介してJythonをJavaに埋め込みます。 `ScriptEngine` APIはGraalPyのオプションと機能に適していないため、このアプローチはお勧めしません。 ただし、既存のコードを移行するために、プロジェクトにインライン化できるScriptEngine実装の例を提供しています。 詳細については、埋め込みのリファレンスマニュアルを参照してください。

お問い合わせ