互通性

除了主要建議在您的 Java 應用程式中使用之外,GraalPy 還可以與其他 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 主機符號,表示 Java 類別的建構函式和靜態成員 (如 java.type 取得的),則傳回 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 腳本與其他動態語言互動 #

更一般而言,從 Python 腳本與其他語言進行非 JVM 特定互動是透過 polyglot API 來完成的。這包括透過 Truffle 框架 支援的所有與動態語言的互動,包括 JavaScript 和 Ruby。

安裝其他動態語言 #

可以透過使用它們各自的 Maven 相依性來包含其他語言,方法與 GraalPy 相同。例如,如果您已經使用 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. 從 polyglot 範圍匯入 glocal 值
    Math = polyglot.import_value("JSMath")
    

    此全域值應如預期般運作

    • 存取屬性會從 polyglot 成員命名空間讀取
      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 互動有著限制。

Interop 類型到 Python #

最重要且最先說明的是:傳遞到 Python 的所有外部物件都具有 Python 類型 foreign。沒有將 (例如) 屬於互通類型「布林值」的物件模擬為具有 Python 類型 bool。這是因為互通類型可以以 Python 內建類型無法做到的方式重疊,而且我們尚未定義哪個類型應優先處理這種情況。不過,我們預期未來會改變這一點。目前,foreign 類型定義了所有 Python 特殊方法,用於在直譯器中使用的類型轉換 (例如 __add____int____str____getitem__ 等方法),並且這些方法會嘗試根據互通類型「正確執行」(或引發例外狀況)。

下表中未列出的類型在 Python 中沒有特殊的解釋。

Interop 類型 Python 解釋
null null 就像 None。請務必知道:互通 null 值都與 None 相同。JavaScript 定義兩個「類似 null」的值:undefinednull,它們 相同,但傳遞到 Python 時,它們會被視為相同。
boolean boolean 的行為與 Python 布林值類似,包括在 Python 中,所有布林值也是整數 (true 為 1,false 為 0)。
number number 的行為與 Python 數字類似。Python 只有一個整數和一個浮點類型,但範圍會以某些方式匯入,例如類型化的陣列。
string 行為與 Python 字串相同。
buffer 緩衝區也是 Python 原生 API 中的概念 (儘管略有不同)。在某些情況下 (例如 memoryview),互通緩衝區的處理方式與 Python 緩衝區相同,以避免資料複本。
array array 可以與 Python 清單相同的方式使用下標存取,並使用整數和切片作為索引。
hash hash 可以與 Python 字典相同的方式使用下標存取,並使用任何「可雜湊」的物件作為索引。「可雜湊」遵循 Python 語意:一般而言,每個具有身分識別的互通類型都被視為「可雜湊」。請注意,如果互通物件屬於 Array Hash 類型,則下標存取的行為未定義。
members 可以使用傳統的 Python . 標記法或 getattr 和相關函式讀取 members 類型的物件。
iterable iterable 的處理方式與任何具有 __iter__ 方法的 Python 物件相同。也就是說,它可以循環使用,以及在其他接受 Python 可疊代項目的位置中使用。
iterator iterator 的處理方式與任何具有 __next__ 方法的 Python 物件相同。
exception 可以在泛型的 except 子句中攔截 exception
MetaObject 元物件可以用於子類型和 isinstance 檢查。
executable executable 物件可以作為函式執行,但永遠不能使用關鍵字引數。
instantiable instantiable 物件可以像 Python 類型一樣呼叫,但永遠不能使用關鍵字引數。

Python 到 Interop 類型 #

Interop 類型 Python 解釋
null 只有 None
boolean 只有 Python bool 的子類型。請注意,與 Python 語意相反,Python bool 絕不 同時也是互通數字。
number 只有 intfloat 的子類型。
string 只有 str 的子類型。
array 任何具有 __getitem____len__ 方法的物件,但如果它同時具有 keysvaluesitems 方法 (與 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 的目的是讓自訂/使用者定義類型能夠參與互通生態系統。這對於預設與互通協定不相容的外部類型特別有用。這方面的一個範例是 numpy 數值類型 (例如 numpy.int32),互通協定預設不支援這些類型。

API #

函式 描述
register_interop_behavior 以接收器類型作為第一個引數。其餘的關鍵字引數對應於各自的互通訊息。並非所有互通訊息都受到支援。
get_registered_interop_behavior 以接收器類型作為第一個引數。傳回給定類型的擴充互通訊息清單。
@interop_behavior 類別裝飾器,僅以接收器類型作為引數。互通訊息是透過已裝飾類別 (供應商) 中定義的靜態方法來擴充。

支援的訊息

大多數(有一些例外)的互通訊息都由互通行為擴展 API 支援,如下表所示。
register_interop_behavior 關鍵字引數的命名慣例遵循 snake_case 命名慣例,例如,互通訊息 fitsInLong 會變成 fits_in_long,以此類推。每個訊息都可以使用一個純 Python 函式(不允許預設關鍵字引數、自由變數和 cell 變數)或一個布林常數來擴展。下表描述了支援的互通訊息

訊息 擴展引數名稱 預期回傳類型
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;
        }
    }
}

與我們聯繫