相互運用性

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 構文を使用することもできます。

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 のインスタンスである場合(class は外部オブジェクトクラスである必要があります)、True を返します。
is_function(obj) obj が interop を使用してラップされた Java ホスト言語関数である場合、True を返します。
is_object(obj) obj が interop を使用してラップされた Java ホスト言語オブジェクトの場合、True を返します。
is_symbol(obj) objjava.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)

他のプログラミング言語との相互運用性について詳しくは、ポリグロットプログラミングおよび言語の埋め込みをご覧ください。

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

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

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

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

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>24.1.0</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 と他の言語間の型マッピング #

interop プロトコルは、さまざまな「型」を定義しており、それらはあらゆる方法で重複する可能性があり、Python との相互作用方法に制限があります。

Python への Interop 型 #

最も重要で、最初に言っておくべきことは、Python に渡されるすべての外部オブジェクトは、Python 型 foreign を持つということです。たとえば、interop 型が「boolean」のオブジェクトを Python 型 bool にエミュレートすることはありません。これは、interop 型が Python の組み込み型では不可能な方法で重複する可能性があり、どの型を優先すべきか、またそのような状況をまだ定義していないためです。ただし、将来的にはこれを変更する予定です。今のところ、foreign 型は、インタープリター全体で使用される型変換のためのすべての Python 特殊メソッド(__add____int____str____getitem__ など)を定義しており、これらは interop 型に基づいて「正しいことをしようとする」(または例外を発生させる)。

以下の表にリストされていない型は、Python での特別な解釈はありません。

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

Interop 型への Python #

Interop 型 Python の解釈
null None のみ。
boolean Python bool のサブタイプのみ。Python セマンティクスとは対照的に、Python bool決して interop 数値でもないことに注意してください。
number int および float のサブタイプのみ。
string str のサブタイプのみ。
array __getitem__ および __len__ メソッドを持つオブジェクト。ただし、keysvalues、および items メソッドも持つ場合は除きます(dict と同様)。
hash dict のサブタイプのみ。
members 任意の Python オブジェクト。読み取り/書き込みのルールは少しアドホックであることに注意してください。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 の目的は、カスタム/ユーザー定義型が interop エコシステムに参加できるようにすることです。これは、interop プロトコルとデフォルトでは互換性がない外部型に特に役立ちます。この意味での例は、interop プロトコルでデフォルトでサポートされていない numpy 数値型(たとえば、numpy.int32)です。

API #

関数 説明
register_interop_behavior 最初の引数としてレシーバーを取ります。残りのキーワード引数は、それぞれの interop メッセージに対応します。すべての interop メッセージがサポートされているわけではありません。
get_registered_interop_behavior 最初の引数としてレシーバーを取ります。指定された型の拡張された interop メッセージのリストを返します。
@interop_behavior クラスデコレーターは、唯一の引数としてレシーバーを取ります。interop メッセージは、デコレートされたクラス(サプライヤー)で定義された静的メソッドを介して拡張されます。

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

次の表に示すように、interop メッセージの大部分(いくつかの例外を除く)は、interop 動作拡張 API でサポートされています。
register_interop_behavior キーワード引数の命名規則は、snake_case の命名規則に従います。つまり、interop fitsInLong メッセージは fits_in_long になります。各メッセージは、純粋な Python 関数(デフォルトのキーワード引数、フリー変数、およびセル変数は許可されていません)またはブール定数で拡張できます。次の表に、サポートされている interop メッセージについて説明します。

メッセージ 拡張引数名 予想される戻り値の型
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 文字列 (タイムゾーン) または整数 (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デコレータがより便利かもしれません。相互運用メッセージの拡張は、デコレートされたクラスの静的メソッドを介して実現されます。静的メソッドの名前は、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;
        }
    }
}

お問い合わせ