相互運用性
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) |
objがclassのインスタンスである場合、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>
サンプル
- 他の言語と対話するには、
polyglotモジュールをインポートします:import polyglot - 別の言語でインライン化されたコードを評価します:
assert polyglot.eval(string="1 + 1", language="js") == 2 - ファイルからコードを評価します:
with open("./my_js_file.js", "w") as f: f.write("Polyglot.export('JSMath', Math)") polyglot.eval(path="./my_js_file.js", language="js") - ポリグロット・スコープからグローカル値をインポートします:
Math = polyglot.import_value("JSMath")その後、このグローバル値は予期したとおりに動作します:
- 属性にアクセスすると、ポリグロット・メンバー・ネームスペースから読み取ります:
assert Math.E == 2.718281828459045 - 結果に対してメソッドをコールすると、直接
invokeが試行され、メンバーの読取りと実行の試行にフォールバックします。assert Math.toString() == "[object Math]" - アイテムへのアクセスは、文字列と数値の両方でサポートされています。
assert Math["PI"] == 3.141592653589793
- 属性にアクセスすると、ポリグロット・メンバー・ネームスペースから読み取ります:
- 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フレームワークに実装された言語)に公開できます。
- Pythonから他の言語にオブジェクトをエクスポートして、他の言語がそれをインポートできるようにすることができます:
import ssl polyglot.export_value(value=ssl, name="python_ssl")(たとえば)JavaScriptコードからそれを使用します:
Polyglot.import('python_ssl).get_server_certificate(["oracle.com", 443]) - 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は、undefinedとnullという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__メソッドを持つ任意のオブジェクト。ただし、keys、valuesおよび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;
}
}
}