相互運用性
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;
}
}
}