我正在尝试将 Java 项目中的 Python 脚本执行从 Jython 迁移到 GraalVM Polyglot。我面临的问题是我不知道如何使我的 Java 类对从这些 Python 脚本导入可见。以下 JUnit 测试表明该脚本在 Jython 上运行良好,但在 GraalVM Pythong 支持下无法运行:
package org.my.graalvm.python.issue;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.junit.jupiter.api.Test;
public class GraalVmPythonTests {
static final String SCRIPT = """
import org.my.graalvm.python.issue.GraalVmPythonTests.MyData as MyData
result=MyData('some data')
""";
@Test
void runPythonScriptWithJavaImportOnJython() throws ScriptException {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("python");
Bindings bindings = new SimpleBindings();
scriptEngine.eval(SCRIPT, bindings);
System.out.println(bindings.get("result"));
}
@Test
void runPythonScriptWithJavaImportOnGraalVmPolyglot() {
try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
Source source = Source.create("python", SCRIPT);
Object result = context.eval(source).as(Object.class);
System.out.println(result);
}
}
public record MyData(String value) {
}
}
该项目的依赖项包括:
dependencies {
testImplementation 'org.python:jython-standalone:2.7.4'
testImplementation 'org.graalvm.sdk:graal-sdk:24.0.2'
testImplementation 'org.graalvm.polyglot:python:24.0.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.1'
}
根据官方 GraalVM文档,我们可以使用一些--python.EmulateJython
命令行参数。但我不确定如何从 JUnit 测试中传递它。此外,由于我们正在完全放弃 Jython,因此此选项感觉不太合适,因此希望有更顺畅的方式来配置 PolyglotContext
以了解我们的 Java 类。
那么,问题是:如何使用 Java 中的 GraalVM Polyglot 配置才能使我们的 Java 类可以通过 Python 脚本导入来使用?
更新
在 Steves 的帮助下,我取得了一些进展。所以,现在我的脚本如下所示:
import java
MyData = java.type("org.my.graalvm.python.issue.GraalVmPythonTests.MyData")
result = MyData("some data")
即使这样执行:
@Test
void runPythonScriptWithJavaImportOnGraalVmPolyglot() {
Context.Builder contextBuilder =
Context.newBuilder("python")
.allowAllAccess(true)
.hostClassLoader(getClass().getClassLoader());
try (Context context = contextBuilder.build()) {
Source source = Source.create("python", SCRIPT);
Object result = context.eval(source).as(Object.class);
System.out.println(result);
}
}
它仍然会失败,像这样:
KeyError: host symbol org.my.graalvm.python.issue.GraalVmPythonTests.MyData is not defined or access has been denied
at <python> <module>(Unknown)
at org.graalvm.polyglot.Context.eval(Context.java:402)
at org.my.graalvm.python.issue.GraalVmPythonTests.runPythonScriptWithJavaImportOnGraalVmPolyglot(GraalVmPythonTests.java:40)
显然配置中缺少其他东西。
.option("python.EmulateJython", "true")
对脚本进行如下修改后,它就可以正常工作了:
from org.my.graalvm.python.issue.GraalVmPythonTests import MyData
result = MyData('some data')
测试主体现在是这样的:
@Test
void runPythonScriptWithJavaImportOnGraalVmPolyglot() {
Context.Builder contextBuilder =
Context.newBuilder()
.allowAllAccess(true)
.option("python.EmulateJython", "true");
try (Context context = contextBuilder.build()) {
Source source = Source.create("python", SCRIPT);
Object result = context.eval(source).as(Object.class);
System.out.println(((Map<?, ?>) result).get("result"));
}
}
更新2
因此,显然 Python 中的内部类不是通过这种方式解析的。因此,这是一个不使用 Jython 的工作示例:
package org.my.graalvm.python.issue;
import java.util.Map;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
public class GraalVmPythonTests {
static final String SCRIPT = """
import java
GraalVmPythonTests = java.type("org.my.graalvm.python.issue.GraalVmPythonTests")
result = GraalVmPythonTests.MyData('some data')
""";
@Test
void runPythonScriptWithJavaImportOnGraalVmPolyglot() {
Context.Builder contextBuilder =
Context.newBuilder()
.allowAllAccess(true);
try (Context context = contextBuilder.build()) {
Source source = Source.create("python", SCRIPT);
Object result = context.eval(source).as(Object.class);
assertInstanceOf(MyData.class, (((Map<?, ?>) result).get("result")));
}
}
public record MyData(String value) {
}
}