互操作性

除了主要推荐在 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 的实例,则返回 Trueclass 必须是外部对象类)
is_function(obj) 如果 obj 是使用互操作包装的 Java 宿主语言函数,则返回 True
is_object(obj) 如果 obj 是使用互操作包装的 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 脚本中与其他动态语言交互 #

通过 polyglot API 可以实现从 Python 脚本到其他语言的更通用、非 JVM 特定的交互。这包括通过 Truffle 框架 支持的所有动态语言交互,包括 JavaScript 和 Ruby。

安装其他动态语言 #

其他语言可以通过在与 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. 从 polyglot 范围导入全局值
    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 的交互方式有限制。

互操作类型到 Python #

最重要的是:传递到 Python 的所有外部对象都具有 Python 类型 foreign。没有(例如)将类型为“boolean”的互操作对象的模拟为具有 Python 类型 bool。这是因为互操作类型可以以 Python 内置类型无法实现的方式重叠,而且我们还没有定义哪种类型应该优先考虑以及在这种情况下应该优先考虑哪种类型。然而,我们预计将来会改变这一点。目前,foreign 类型定义了用于整个解释器中的类型转换的 Python 所有特殊方法(例如 __add____int____str____getitem__ 等等),并且这些方法会根据互操作类型尝试“执行正确的事情”(或者引发异常)。

下面表格中未列出的类型在 Python 中没有特殊解释。

互操作类型 Python 解释
null null 类似于 None。重要的是要知道:互操作 null 值与 None 完全相同。JavaScript 定义了两个“类似 null”的值;undefinednull,它们相同,但当传递给 Python 时,它们会被视为相同。
boolean boolean 的行为与 Python 布尔值相同,包括 Python 中所有布尔值也是整数(分别为真和假的 1 和 0)这一事实。
number number 的行为与 Python 数字相同。Python 只有一个整数类型和一个浮点类型,但范围在某些地方导入,例如类型化数组。
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 元对象可以在子类型和 isinstance 检查中使用。
executable executable 对象可以作为函数执行,但永远不会使用关键字参数执行。
instantiable instantiable 对象可以像 Python 类型一样调用,但永远不会使用关键字参数调用。

Python 到互操作类型 #

互操作类型 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* 命名约定,即 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 可用于为现有类型注册 interop 行为。

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 装饰器可能更方便。Interop 消息扩展是通过装饰类的 **静态** 方法实现的。静态方法的名称与 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;
        }
    }
}

联系我们