相互運用性

GraalPyは、主にJavaアプリケーションで使用することをお薦めしますが、他のGraal言語(Truffleフレームワークに実装されている言語)と相互運用できます。つまり、これらの他の言語によって提供されるオブジェクトおよび関数をPythonスクリプトから直接使用できます。

PythonスクリプトからのJavaとの対話

JavaはJVMのホスト言語であり、GraalPyインタプリタ自体を実行します。PythonスクリプトからJavaと相互運用するには、javaモジュールを使用します:

import java
BigInteger = java.type("java.math.BigInteger")
myBigInt = BigInteger.valueOf(42)
# a public Java methods can just be called
myBigInt.shiftLeft(128) # returns a <JavaObject[java.math.BigInteger] at ...>
# Java method names that are keywords in Python must be accessed using `getattr`
getattr(myBigInt, "not")() # returns a <JavaObject[java.math.BigInteger] at ...>
byteArray = myBigInt.toByteArray()
# Java arrays can act like Python lists
assert len(byteArray) == 1 and byteArray[0] == 42

javaネームスペースからパッケージをインポートするには、従来のPythonインポート構文を使用することもできます:

import java.util.ArrayList
from java.util import ArrayList
assert java.util.ArrayList == ArrayList

al = ArrayList()
al.add(1)
al.add(12)
assert list(al) == [1, 12]

type組込みメソッドに加えて、javaモジュールは次のメソッドを公開しています:

組込み 仕様
instanceof(obj, class) objclassのインスタンスである場合、Trueを返します(classは外部オブジェクト・クラスである必要があります)
is_function(obj) objが、interopを使用してラップされたJavaホスト言語関数である場合、Trueを返します
is_object(obj) obj引数が、interopを使用してラップされたJavaホスト言語オブジェクトである場合、Trueを返します
is_symbol(obj) obj引数が、java.typeによって取得されたJavaクラスのコンストラクタおよび静的メンバーを表すJavaホスト・シンボルである場合、Trueを返します
ArrayList = java.type('java.util.ArrayList')
my_list = ArrayList()
assert java.is_symbol(ArrayList)
assert not java.is_symbol(my_list)
assert java.is_object(ArrayList)
assert java.is_function(my_list.add)
assert java.instanceof(my_list, ArrayList)

他のプログラミング言語との相互運用性の詳細は、『Polyglot Programming』および『Embed Languages』を参照してください。

Pythonスクリプトからの他の動的言語との対話

Pythonスクリプトの他の言語とのより一般的な非JVM固有の対話は、ポリグロットAPIを介して実現されます。これには、JavaScriptやRubyなど、Truffleフレームワークを介してサポートされている動的言語とのすべての対話が含まれます。

他の動的言語のインストール

他の言語を含めるには、GraalPyと同じ方法でそれぞれのMaven依存関係を使用します。たとえば、GraalPyを使用してMavenプロジェクトをすでに構成している場合は、次の依存関係を追加してJavaScriptにアクセスします:

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>23.1.2</version>
</dependency>

サンプル

  1. 他の言語と対話するには、polyglotモジュールをインポートします:
    import polyglot
    
  2. 別の言語でインライン化されたコードを評価します:
    assert polyglot.eval(string="1 + 1", language="js") == 2
    
  3. ファイルからコードを評価します:
    with open("./my_js_file.js", "w") as f:
        f.write("Polyglot.export('JSMath', Math)")
    polyglot.eval(path="./my_js_file.js", language="js")
    
  4. ポリグロット・スコープからグローカル値をインポートします:
    Math = polyglot.import_value("JSMath")
    

    その後、このグローバル値は予期したとおりに動作します:

    • 属性にアクセスすると、ポリグロット・メンバー・ネームスペースから読み取ります:
      assert Math.E == 2.718281828459045
      
    • 結果に対してメソッドをコールすると、直接invokeが試行され、メンバーの読取りと実行の試行にフォールバックします。
      assert Math.toString() == "[object Math]"
      
    • アイテムへのアクセスは、文字列と数値の両方でサポートされています。
      assert Math["PI"] == 3.141592653589793
      
  5. JavaScript正規表現エンジンを使用して、Python文字列を照合します:
    js_re = polyglot.eval(string="RegExp()", language="js")
    
    pattern = js_re.compile(".*(?:we have (?:a )?matching strings?(?:[!\\?] )?)(.*)")
    
    if pattern.exec("This string does not match"): raise SystemError("that shouldn't happen")
    
    md = pattern.exec("Look, we have matching strings! This string was matched by Graal.js")
    
    assert "Graal.js" in md[1]
    

    このプログラムは、JavaScript正規表現オブジェクトを使用してPython文字列を照合します。Pythonは、取得したグループをJavaScriptの結果から読み取り、その中の部分文字列をチェックします。

他の言語へのPythonオブジェクトのエクスポート

polyglotモジュールを使用すると、PythonオブジェクトをJVM言語やその他のGraal言語(Truffleフレームワークに実装された言語)に公開できます。

  1. Pythonから他の言語にオブジェクトをエクスポートして、他の言語がそれをインポートできるようにすることができます:
    import ssl
    polyglot.export_value(value=ssl, name="python_ssl")
    

    (たとえば)JavaScriptコードからそれを使用します:

    Polyglot.import('python_ssl).get_server_certificate(["oracle.com", 443])
    
  2. Python関数を装飾して名前でエクスポートできます:
    @polyglot.export_value
    def python_method():
        return "Hello from Python!"
    

    (たとえば)Javaコードから使用します:

    import org.graalvm.polyglot.*;
    
    class Main {
        public static void main(String[] args) {
            try (var context = Context.create()) {
                context.eval(Source.newBuilder("python", "file:///python_script.py").build());
    
                String result = context.
                    getPolyglotBindings().
                    getMember("python_method").
                    execute().
                    asString();
                assert result.equals("Hello from Python!");
            }
        }
     }
    

Pythonと他の言語の間の型のマッピング

相互運用性プロトコルは、あらゆる種類の方法で重複し、Pythonとの相互運用に制限を与える可能性のある、様々な「型」を定義します。

相互運用性の型からPythonへ

最も重要で明らかなことは、Pythonに渡されるすべての外部オブジェクトはPython型foreignを持つということです。(たとえば)相互運用性の型が"boolean"のオブジェクトをPython型boolにするエミュレーションはありません。これは、相互運用性の型がPythonの組込み型ではできないような重複をすることがあり、どの型が優先されるべきか、およびそのような状況がまだ定義されていないためです。しかし、将来的にはこれを変える予定です。現時点では、foreign型は、インタプリタ全体で使用される型変換のためのすべてのPython特殊メソッド(__add____int____str____getitem__などのメソッド)を定義します。これらは、相互運用性の型に基づいて適切な処理を試行します(または例外を呼び出します)。

次の表に示されていない型は、Pythonでの特別な解釈はありません。

相互運用性の型 Pythonの解釈
null nullは、Noneのようなものです。重要事項: 相互運用性のnull値はすべてNoneと同じです。JavaScriptは、undefinednullというnullに似た2つの値(同一ではないが、Pythonに渡されると、同一として扱われる)を定義します。
boolean booleanは、Pythonではすべてのブールは整数でもある(trueの場合は1、falseの場合は0)という事実を含め、Pythonのブールと同様に動作します。
number numberはPythonの数値と同様に動作します。Pythonには1つの整数型と1つの浮動小数点型のみがありますが、範囲は型付き配列などの一部の場所にインポートされます。
string Python文字列と同じように動作します。
buffer バッファは、PythonのネイティブAPIの概念でもあります(若干異なります)。相互運用性のバッファは、データのコピーを回避するために、一部の場所(memoryviewなど)ではPythonのバッファと同じように扱われます。
array arrayは、は、整数とスライスを添え字として、Pythonのリストと同じように添字アクセスで使用できます。
hash hashは、任意のハッシュ可能オブジェクトをキーとして、Pythonディクショナリと同じように添字アクセスで使用できます。"ハッシュ可能"はPythonセマンティクスに従います。一般に、アイデンティティを持つすべての相互運用性型は"ハッシュ可能"とみなされます。相互運用オブジェクトの型がArrayおよびHashの場合、添え字アクセスの動作は未定義です。
members members型のオブジェクトは、従来のPython .表記法またはgetattrおよび関連関数を使用して読み取ることができます。
iterable iterableは、__iter__メソッドを持つPythonオブジェクトと同じ方法で処理されます。つまり、ループや、Pythonイテラブルを受け入れる他の場所で使用できます。
iterator iteratorは、__next__メソッドを持つPythonオブジェクトと同じ方法で処理されます。
exception exceptionは、汎用のexcept句で捕捉できます。
MetaObject メタ・オブジェクトは、subtypeおよびisinstanceチェックで使用できます。
executable executableオブジェクトは関数として実行できますが、キーワード引数とともに実行することはできません。
instantiable instantiableオブジェクトは、Pythonの型と同様にコールできますが、キーワード引数ではコールできません。

Pythonから相互運用性の型へ

相互運用性の型 Pythonの解釈
null Noneのみ。
boolean Python boolのサブタイプのみ。Pythonセマンティクスとは対照的に、Python boolが同時に相互運用性の数値になることはありません
number intおよびfloatのサブタイプのみ。
string strのサブタイプのみ。
array __getitem__および__len__メソッドを持つ任意のオブジェクト。ただし、keysvaluesおよびitemsメソッドも持っている場合は該当しません(dictと同様)。
hash dictのサブタイプのみ。
members 任意のPythonオブジェクト。readable/writableのルールは、Python MOPの一部でないことを確認するため、若干非定型です。
iterable __iter__または__getitem__メソッドを持つ任意のPythonオブジェクト。
iterator __next__メソッドを持つ任意のPythonオブジェクト。
exception 任意のPython BaseExceptionサブタイプ。
MetaObject 任意のPython type
executable __call__メソッドを持つ任意のPythonオブジェクト。
instantiable 任意のPython type

相互運用性拡張API

polyglotモジュールで定義された単純なAPIを介して、相互運用性プロトコルをPythonから直接拡張できます。このAPIの目的は、カスタム/ユーザー定義型が相互運用エコシステムに参加できるようにすることです。これは、デフォルトでは相互運用プロトコルと互換性がない外部型に特に役立ちます。この意味での例は、numpy数値型(たとえば、numpy.int32)で、デフォルトでは相互運用プロトコルでサポートされていません。

API

関数 説明
register_interop_behavior 受信側のtypeを最初の引数として取ります。残りのキーワード引数は、それぞれの相互運用性メッセージに対応します。すべての相互運用性メッセージがサポートされているわけではありません。
get_registered_interop_behavior 受信側のtypeを最初の引数として取ります。指定されたタイプの拡張相互運用性メッセージのリストを返します。
@interop_behavior クラス・デコレータ。受信側のtypeを唯一の引数とします。相互運用性メッセージは、装飾されたクラス(サプライヤ)で定義されたstaticメソッドによって拡張されます。

サポートされているメッセージ

次の表に示すように、相互運用性メッセージの大部分(一部例外あり)は相互運用性拡張APIでサポートされています。
register_interop_behaviorキーワード引数の命名規則は、snake_case命名規則に従います。つまり、相互運用性のfitsInLongメッセージはfits_in_longになります。各メッセージは、純粋なpython関数(デフォルトのキーワード引数、自由変数、セル変数は許可されません)またはブール定数を使用して拡張できます。次の表に、サポートされる相互運用性メッセージを示します:

メッセージ 拡張引数名 期待される戻り型
isBoolean is_boolean bool
isDate is_date bool
isDuration is_duration bool
isIterator is_iterator bool
isNumber is_number bool
isString is_string bool
isTime is_time bool
isTimeZone is_time_zone bool
isExecutable is_executable bool
fitsInBigInteger fits_in_big_integer bool
fitsInByte fits_in_byte bool
fitsInDouble fits_in_double bool
fitsInFloat fits_in_float bool
fitsInInt fits_in_int bool
fitsInLong fits_in_long bool
fitsInShort fits_in_short bool
asBigInteger as_big_integer int
asBoolean as_boolean bool
asByte as_byte int
asDate as_date 次の要素を含む3タプル: (year: int、month: int、day: int)
asDouble as_double float
asDuration as_duration 次の要素を含む2タプル: (seconds: long、nano_adjustment: long)
asFloat as_float float
asInt as_int int
asLong as_long int
asShort as_short int
asString as_string str
asTime as_time 次の要素を含む4タプル: (hour: int、minute: int、second: int、microsecond: int)
asTimeZone as_time_zone 文字列(タイムゾーン)またはint(秒単位のutcデルタ)
execute execute object
readArrayElement read_array_element object
getArraySize get_array_size int
hasArrayElements has_array_elements bool
isArrayElementReadable is_array_element_readable bool
isArrayElementModifiable is_array_element_modifiable bool
isArrayElementInsertable is_array_element_insertable bool
isArrayElementRemovable is_array_element_removable bool
removeArrayElement remove_array_element NoneType
writeArrayElement write_array_element NoneType
hasIterator has_iterator bool
hasIteratorNextElement has_iterator_next_element bool
getIterator get_iterator pythonイテレータ
getIteratorNextElement get_iterator_next_element object
hasHashEntries has_hash_entries bool
getHashEntriesIterator get_hash_entries_iterator pythonイテレータ
getHashKeysIterator get_hash_keys_iterator pythonイテレータ
getHashSize get_hash_size int
getHashValuesIterator get_hash_values_iterator pythonイテレータ
isHashEntryReadable is_hash_entry_readable bool
isHashEntryModifiable is_hash_entry_modifiable bool
isHashEntryInsertable is_hash_entry_insertable bool
isHashEntryRemovable is_hash_entry_removable bool
readHashValue read_hash_value object
writeHashEntry write_hash_entry NoneType
removeHashEntry remove_hash_entry NoneType

使用例

単純なregister_interop_behavior APIを使用して、既存のタイプの相互運用動作を登録できます:

import polyglot
import numpy

polyglot.register_interop_behavior(numpy.int32,
    is_number=True,
    fitsInByte=lambda v: -128 <= v < 128,
    fitsInShort=lambda v: -0x8000 <= v < 0x8000
    fitsInInt=True,
    fitsInLong=True,
    fitsInBigInteger=True,
    asByte=int,
    asShort=int,
    asInt=int,
    asLong=int,
    asBigInteger=int,
)

@interop_behaviorデコレータは、より多くの動作を宣言する場合に便利です。相互運用性メッセージ拡張は、装飾されたクラスのstaticメソッドによって実現されます。静的メソッドの名前は、register_interop_behaviorで想定されるキーワード名と同じです。

from polyglot import interop_behavior
import numpy

@interop_behavior(numpy.float64)
class Int8InteropBehaviorSupplier:
    @staticmethod
    def is_number(_): 
        return True

    @staticmethod
    def fitsInDouble(_):
        return True

    @staticmethod
    def asDouble(v):
        return float(v)

埋め込まれた場合、両方のクラスが予想どおりに動作します:

import java.nio.file.Files;
import java.nio.file.Path;
import org.graalvm.polyglot.Context;

class Main {
    public static void main(String[] args) {
        try (var context = Context.create()) {
            context.eval("python", Files.readString(Path.of("path/to/interop/behavior/script.py")));
            assert context.eval("python", "numpy.float64(12)").asDouble() == 12.0;
            assert context.eval("python", "numpy.int32(12)").asByte() == 12;
        }
    }
}