在 JVM 上开始使用 GraalPy
您可以在 GraalVM JDK、Oracle JDK 或 OpenJDK 上使用 GraalPy。您可以使用 Maven 或 Gradle 构建工具轻松地将 GraalPy 添加到您的 Java 应用程序中,如下所示。其他构建系统(Ant、Make、CMake 等)也可以使用,但需要更多手动工作。
Maven
GraalPy 可以生成一个 Maven 项目,该项目使用 Maven 工件 将 Python 包嵌入到 Java 应用程序中。
-
GraalPy 项目发布了一个 Maven 原型来生成启动项目:
mvn archetype:generate \ -DarchetypeGroupId=org.graalvm.python \ -DarchetypeArtifactId=graalpy-archetype-polyglot-app \ -DarchetypeVersion=25.0.0
-
使用自动添加的 GraalVM Native Image "工具" 插件构建原生可执行文件:
mvn -Pnative package
-
完成后,运行可执行文件:
./target/polyglot_app
应用程序会在控制台上打印 "hello java"。
该项目使用 GraalVM Polyglot API 以及额外功能来管理 Python 虚拟环境并将 Python 包依赖项与 Maven 工作流集成。Java 代码和 pom.xml 文件被详细记录,生成的代码描述了可用功能。
另请参阅 嵌入构建工具 获取有关 GraalPy Maven 插件的更多信息。
使用原生 Python 包创建跨平台 JAR 文件
生成的项目使用 GraalPy Maven 插件,这使得添加 Python 依赖项变得容易。但是,Python 包可能具有特定于构建系统的原生组件。为了将生成的应用程序分发到其他系统,请按照以下步骤操作:
-
在每个部署平台上构建项目。重命名 JAR 文件,使每个文件都有特定于平台的名称,并将它们移动到同一台机器上的临时目录中。
-
解压每个 JAR 文件(用正确的 JAR 文件名替换)。需要从每个 JAR 文件中连接一个特殊文件 vfs/fileslist.txt。最后,从所有文件的组合以及连接的 fileslist.txt 创建一个新的 combined.jar。
unzip linux.jar -d combined mv combined/vfs/fileslist.txt fileslist-linux.txt unzip windows.jar -d combined mv combined/vfs/fileslist.txt fileslist-windows.txt cat fileslist-linux.txt fileslist-windows.txt > combined/vfs/fileslist.txt cd combined zip -r ../combined.jar *
Gradle
-
使用下面的命令创建一个 Gradle Java 应用程序并按照提示操作(选择 Groovy 构建脚本语言、选择测试框架等):
gradle init --type java-application \ --project-name interop \ --package interop \ --no-split-project
项目在当前工作目录中生成,具有以下结构:
└── app ├── build.gradle └── src └── main ├── java │ └── interop │ └── App.java └── resources
-
打开项目配置文件 app/build.gradle,并按如下方式修改它。
-
在
dependencies
部分包含 GraalPy 支持和 GraalVM Polyglot API:implementation("org.graalvm.polyglot:polyglot:25.0.0") implementation("org.graalvm.polyglot:python:25.0.0")
-
-
最后,将名为 App.java 的文件中的代码替换为以下内容,以实现一个小的 Python 嵌入:
package interop; import org.graalvm.polyglot.*; class App { public static void main(String[] args) { try (var context = Context.create()) { System.out.println(context.eval("python", "'Hello Python!'").asString()); } } }
-
使用 Gradle 运行应用程序:
./gradlew run
应用程序会在控制台上打印 "Hello Python!"。
注意:GraalPy 运行时的性能取决于您嵌入它的 JDK。有关更多信息,请参阅 运行时优化支持。
-
可选地,您也可以使用第三方 Python 包。
5.1. 在 app/build.gradle 中:
-
将 graalpy-gradle-plugin 添加到
plugins
部分:id "org.graalvm.python" version "25.0.0"
-
配置 GraalPy Gradle 插件:
graalPy { packages = ["termcolor==2.2"] }
5.2. 在 settings.gradle 中,添加以下
pluginManagement
配置。pluginManagement { repositories { gradlePluginPortal() } }
5.3. 更新名为 App.java 的文件如下:
package interop; import org.graalvm.polyglot.*; import org.graalvm.python.embedding.GraalPyResources; class App { ... public static void main(String[] args) { try (Context context = GraalPyResources.createContext()) { String src = """ from termcolor import colored colored_text = colored("hello java", "red", attrs=["reverse", "blink"]) print(colored_text) """; context.eval("python", src); } }
-
另请参阅 嵌入构建工具 获取有关 GraalPy Gradle 插件的更多信息。
Ant、CMake、Makefile 或其他不直接支持 Maven 依赖项的构建系统
一些(通常是较旧的)项目可能使用 Ant、Makefiles、CMake 或其他不直接支持 Maven 依赖项的构建系统。诸如 Apache Ivy™ 等项目使这些构建系统能够解析 Maven 依赖项,但开发人员可能有理由不使用它们。GraalPy 附带了一个工具来从 Maven 获取所需的 JAR 文件。
-
假设有一个目录用于存储项目的第三方依赖项,并且构建系统设置为将那里的任何 JAR 文件放在类路径上,项目目录树可能类似于这样:
├───lib │ └─── ... *.jar 依赖项在这里 └───src └─── ... *.java 文件和资源在这里
-
为您的系统 安装 GraalPy 并确保您的
PATH
中有graalpy
。打开命令行界面并进入您的项目目录。然后,根据您的系统适当运行以下命令之一:在 POSIX shell 中:
export GRAALPY_HOME=$(graalpy -c 'print(__graalpython__.home)') "${GRAALPY_HOME}/libexec/graalpy-polyglot-get" -a python -o lib -v "25.0.0"
在 PowerShell 中:
$GRAALPY_HOME = graalpy -c "print(__graalpython__.home)" & "$GRAALPY_HOME/libexec/graalpy-polyglot-get" -a python -o lib -v "25.0.0"
这些命令将所有 GraalPy 依赖项下载到 lib 目录中。
-
如果您的构建系统设置为从 lib 中选取 JAR 文件,那么下面的 GraalPy 嵌入代码如果放在项目的适当位置作为主类运行应该可以工作。
import org.graalvm.polyglot.*; public class Hello { public static void main(String[] args) { try (var context = Context.newBuilder().option("engine.WarnInterpreterOnly", "false").build()) { System.out.println(context.eval("python", "'Hello Python!'").asString()); } } }
在 GraalPy 上测试 Python 应用程序和包
转到 这里 获取与 CPython 兼容的 GraalPy 发行版来测试 Python 应用程序和包。
相关文档
在 GraalPy 上测试 Python 应用程序和包
选择 GraalPy 运行时
GraalPy 提供符合 Python 3.11 的运行时。主要目标是支持 PyTorch、SciPy 及其组成库,以及与来自丰富的 Python 生态系统的其他数据科学和机器学习库一起工作。GraalPy 作为提前编译的原生可执行文件分发,体积紧凑。
GraalPy 提供以下功能:
- 与 CPython 兼容的分发版。这是在 GraalPy 上测试 Python 代码最兼容的选项,因为它最接近 CPython 分发版的结构。
- Python 应用程序的独特部署模式。将 GraalPy 上的 Python 应用程序编译为 单个原生二进制文件,嵌入所有需要的资源。
- 访问 GraalVM 的语言生态系统和工具。GraalPy 可以运行许多标准 Python 工具以及 GraalVM 生态系统中的工具。
GraalPy 分发版
GraalPy 可用作基于 Oracle GraalVM 构建的 GraalPy 和 GraalPy Community。
- 基于 Oracle GraalVM 构建的 GraalPy 提供最佳体验:它带有额外的优化,速度更快,内存效率更高。它在 GraalVM 免费条款和条件 (GFTC) 许可证下授权,与 Oracle GraalVM 相同,允许任何用户包括商业和生产使用。只要不收费,就允许重新分发。
- GraalPy Community 基于 GraalVM Community Edition 构建,是完全开源的。
Oracle 和 Community 版本的 GraalPy 有两种语言运行时选项:
-
原生
- GraalPy 提前编译为原生可执行文件。
- 这意味着您不需要 JVM 来运行 GraalPy,并且它体积紧凑。
-
JVM
- 您可以轻松利用 Java 互操作性。
- 峰值性能可能高于原生选项。
GraalPy 标识
四种 GraalPy 运行时按以下方式标识,使用一般模式 graalpy(-community)(-jvm)---:
Oracle | Community | |
---|---|---|
原生 | graalpy--- | graalpy-community--- |
JVM | graalpy-jvm--- | graalpy-community-jvm--- |
比较
运行时 | 原生(默认) | JVM |
---|---|---|
启动时间 | 更快 | 更慢 |
达到峰值性能的时间 | 更快 | 更慢 |
峰值性能(也考虑 GC) | 好 | 最佳 |
Java 互操作性 | 需要配置 | 可用 |
安装 GraalPy
注意:GraalPy 发布和在 Pyenv 上可用之间会有延迟。
Linux
在 Linux 上安装 GraalPy 的最简单方法是使用 Pyenv(Python 版本管理器)。要使用 Pyenv 安装版本 25.0.0,请运行以下命令:
pyenv install graalpy-25.0.0
pyenv shell graalpy-25.0.0
在运行
pyenv install
之前,您可能需要更新pyenv
以包含最新的 GraalPy 版本。
或者,您可以从 GitHub 发布 下载压缩的 GraalPy 安装文件。
- 找到匹配模式 graalpy-XX.Y.Z-linux-amd64.tar.gz 或 graalpy-XX.Y.Z-linux-aarch64.tar.gz(取决于您的平台)的下载并下载。
- 解压文件并更新您的
PATH
环境变量以包含 graalpy-XX.Y.Z-linux-amd64/bin(或 graalpy-XX.Y.Z-linux-aarch64/bin)目录。
macOS
在 macOS 上安装 GraalPy 的最简单方法是使用 Pyenv(Python 版本管理器)。要使用 Pyenv 安装版本 25.0.0,请运行以下命令:
pyenv install graalpy-25.0.0
pyenv shell graalpy-25.0.0
在运行
pyenv install
之前,您可能需要更新pyenv
以包含最新的 GraalPy 版本。
或者,您可以从 GitHub 发布 下载压缩的 GraalPy 安装文件。
-
找到匹配模式 graalpy-XX.Y.Z-macos-amd64.tar.gz 或 graalpy-XX.Y.Z-macos-aarch64.tar.gz(取决于您的平台)的下载并下载。
-
删除隔离属性。
sudo xattr -r -d com.apple.quarantine /path/to/graalpy
例如:
sudo xattr -r -d com.apple.quarantine ~/.pyenv/versions/graalpy-25.0.0
-
解压文件并更新您的
PATH
环境变量以包含 graalpy-XX.Y.Z-macos-amd64/bin(或 graalpy-XX.Y.Z-macos-aarch64/bin)目录。
Windows
- 从 GitHub 发布 中找到并下载匹配模式 graalpy-XX.Y.Z-windows-amd64.tar.gz 的压缩 GraalPy 安装文件。
- 解压文件并更新您的
PATH
变量以包含 graalpy-XX.Y.Z-windows-amd64/bin 目录。
Windows 限制
GraalPy 的 Windows 分发版比其 Linux 或 macOS 对应版本有更多限制,因此并非所有功能和包都可用。
它有以下已知问题:
-
JLine 将 Windows 视为哑终端,在 REPL 中没有自动完成和有限的编辑功能
-
REPL 中的交互式
help()
不起作用 -
在虚拟环境中:
- graalpy.cmd 和 graalpy.exe 已损坏
- pip.exe 不能直接使用
pip
在缓存文件加载方面有问题,使用--no-cache-dir
- 只能安装纯 Python 二进制包,没有原生扩展或源码构建
- 要安装包,使用
myvenv/Scripts/python.exe -m pip --no-cache-dir install <pkg>
-
从 PowerShell 运行比从 CMD 运行效果更好,各种脚本在后者上会失败
安装包
使用 GraalPy 的最佳方式是在 venv 虚拟环境中。这会生成包装脚本,并使实现可以从 shell 中用作标准 Python 解释器。
-
通过运行以下命令使用 GraalPy 创建虚拟环境:
graalpy -m venv <venv-dir>
例如:
graalpy -m venv ~/.virtualenvs/graalpy-25.0.0
-
在您的 shell 会话中激活环境:
source <venv-dir>/bin/activate
例如:
source ~/.virtualenvs/graalpy-25.0.0/bin/activate
虚拟环境中提供了多个可执行文件,包括:python
、python3
和 graalpy
。
注意:要停用 Python 环境(并返回到您的 shell),运行
deactivate
。
使用虚拟环境时,pip
包安装器可用。GraalPy 的 pip
实现可能会在它提供补丁以使这些包更好工作的情况下选择非最新的包版本。
Python 性能
执行性能
GraalPy 使用 GraalVM 最先进的即时 (JIT) 编译器。当 JIT 编译时,GraalPy 在官方 Python Performance Benchmark Suite 上运行 Python 代码的速度比 CPython 快约 4 倍。
这些基准测试可以通过安装 pyperformance
包并在 CPython 和 GraalPy 上分别调用 pyperformance run
来运行。为了获得 Jython 数字,我们调整了框架和基准测试,因为 Jython 中缺少 Python 3 支持。然后通过取工作基准测试的成对交集并计算几何平均值来计算加速。
没有 JIT 编译器,GraalPy 目前执行纯 Python 代码的速度比 CPython 慢约 4 倍。这意味着非常短暂运行的脚本或在 Oracle JDK 或 OpenJDK 上没有 Graal 编译器运行的脚本预期会更慢。
机器学习或数据科学生态系统中的许多 Python 包都包含 C 扩展代码。这种代码从 GraalPy 的 JIT 编译中受益很少,并且受到必须在 GraalPy 上模拟 CPython 实现细节的影响。当涉及许多 C 扩展时,性能可能会根据原生和 Python 代码的特定交互而有很大差异。
代码加载性能和占用空间
解析 Python 代码需要时间,因此当使用 GraalPy 在 Python 中嵌入另一种语言时,请观察有关嵌入 Graal 语言的一般建议,这些建议与 代码缓存 相关。此外,一些 Python 库在启动时需要加载大量代码,然后才能完成任何工作。由于 Python 语言的设计,增量解析是不可能的,对于某些脚本,解析器可能代表运行时和内存的重要部分。为了缓解这种情况,如果配置了适当的文件系统访问,GraalPy 可以在 .pyc 文件中缓存解析期间生成的字节码。
创建和管理 .pyc 文件
GraalPy 在存在无效或缺失的 .pyc 文件且与相应的 .py 文件匹配时自动创建 .pyc 文件。
当 GraalPy 在执行期间首次导入 Python 源文件(模块)时,它会自动创建相应的 .pyc 文件。如果 GraalPy 再次导入同一个模块,那么它使用现有的 .pyc 文件。这意味着尚未执行(导入)的源文件没有 .pyc 文件。GraalPy 完全通过 FileSystem API 创建 .pyc 文件,以便具有嵌入式 Python 代码的 Java 应用程序可以管理文件系统访问。
注意:GraalPy 永不删除 .pyc 文件。
每次 GraalPy 随后执行脚本时,它都会重用现有的 .pyc 文件,或创建一个新的。如果原始源文件的时间戳或哈希码发生更改,GraalPy 会重新创建 .pyc 文件。GraalPy 仅通过调用 source.hashCode()
基于 Python 源文件生成哈希码,这是对源文件字节数组的 JDK 哈希码,使用 java.util.Arrays.hashCode(byte[])
计算。
如果 Python 解析器中的魔数发生更改,GraalPy 也会重新创建 .pyc 文件。魔数在 Python 源码中硬编码,用户无法更改(当然,除非该用户可以访问 Python 的字节码)。
GraalPy 的开发人员在字节码格式发生更改时会更改魔数。这是一个实现细节,因此魔数不必对应于 GraalPy 的版本(如在 CPython 中)。pyc
的魔数是正在运行的实际 Python 运行时 Java 代码的函数。魔数的更改在发布说明中传达,以便开发人员或系统管理员可以在升级时删除旧的 .pyc 文件。
请注意,如果您使用 .pyc 文件,至少在切换版本或修改原始源代码文件时,您必须允许 GraalPy 具有写访问权限。否则,源代码文件的重新生成将失败,每次导入都会有访问每个旧 .pyc 文件、解析代码、序列化它并尝试(并失败)写出新 .pyc 文件的开销。
GraalPy 为 .pyc 文件创建以下目录结构:
top_directory/
__pycache__/
sourceA.graalpy.pyc
sourceB.graalpy.pyc
sourceA.py
sourceB.py
sub_directory/
__pycache__/
sourceX.graalpy.pyc
sourceX.py
默认情况下,GraalPy 在与源代码文件相同的目录级别创建 pycache 目录,在此目录中存储来自同一目录的所有 .pyc 文件。此目录可能存储使用不同版本的 Python(例如,包括 CPython)创建的 .pyc 文件,因此用户可能会看到以 .cpython3-6.pyc 结尾的文件。
.pyc 文件主要由 GraalPy 以与 CPython 兼容的方式自动管理。GraalPy 提供类似于 CPython 的选项来指定 .pyc 文件的位置,以及是否应该写入它们,这两个选项都可以由访客代码更改。
.pyc 文件的创建可以以与 CPython 相同的方式 控制:
- GraalPy 启动器(
graalpy
)读取PYTHONDONTWRITEBYTECODE
环境变量。如果设置为非空字符串,Python 将不会在导入模块时尝试创建 .pyc 文件。 - 启动器命令行选项
-B
,如果给定,具有与上述相同的效果。 - 访客语言代码可以在运行时更改
sys
内置模块的属性dont_write_bytecode
以更改后续导入的行为。 - GraalPy 启动器读取
PYTHONPYCACHEPREFIX
环境变量。如果设置,它会在前缀指定的路径创建 pycache 目录,并按需创建源树的目录结构镜像以存储 .pyc 文件。 - 访客语言代码可以在运行时更改
sys
模块的属性pycache_prefix
以更改后续导入的位置。
因为开发人员不能使用环境变量或 CPython 选项将这些选项传达给 GraalPy,这些选项作为语言选项提供:
python.DontWriteBytecodeFlag
- 等效于-B
或PYTHONDONTWRITEBYTECODE
python.PyCachePrefix
- 等效于PYTHONPYCACHEPREFIX
请注意,Python 上下文默认不会启用写入 .pyc 文件。GraalPy 启动器默认启用它,但如果在嵌入用例中需要此功能,应注意确保 pycache 位置得到适当管理,并且该位置的文件以与源代码文件(.py)相同的方式受到保护,防止被操纵。
还要注意,要将应用程序源升级到 Oracle GraalPy,开发人员必须根据需要删除旧的 .pyc 文件。
安全考虑
GraalPy 通过 FileSystem API 执行所有文件操作(获取数据、时间戳和写入 .pyc 文件)。开发人员可以通过自定义(例如,只读)FileSystem
实现来修改所有这些操作。开发人员也可以通过禁用 GraalPy 的 I/O 权限来有效地禁用 .pyc 文件的创建。
如果 .pyc 文件不可读,它们的位置不可写。如果 .pyc 文件的序列化数据或魔数以任何方式损坏,反序列化失败,GraalPy 再次解析 .py 源代码文件。这只会对模块解析带来轻微的性能冲击,对于大多数应用程序来说不应该是显著的(假设应用程序除了加载 Python 代码之外还执行实际工作)。
JVM 上的现代 Python
对于 Python 版本 2(现在 EOL),Jython 是连接 Python 和 Java 的事实上手段。大多数使用 Java 集成的现有 Jython 代码都将基于稳定的 Jython 版本 — 但是,这些仅在 Python 2.x 版本中可用。相比之下,GraalPy 与 Python 3.x 兼容,并且不提供与早期 2.x 版本的 Jython 的完全兼容性。
要将代码从 Python 2 迁移到 Python 3,请遵循 Python 社区的官方指南。一旦您的 Jython 代码与 Python 3 兼容,请遵循本指南来解决 GraalPy 和 Jython 之间的其他差异。
GraalPy 对 Java 互操作性 的一流支持使得从 Python 使用 Java 库尽可能容易,为 Java 代码提供了超越其他 Graal 语言(在 Truffle 框架 上实现的语言)的通用互操作性支持的特殊便利。
并非 Jython 的所有功能都在 GraalPy 上受支持。有些是受支持的,但由于它们对运行时性能的负面影响,默认情况下被禁用。在迁移期间,您可以使用命令行选项启用这些功能:--python.EmulateJython
。然而,我们建议摆脱这些功能,以实现最佳性能。
迁移 Jython 脚本
要将纯 Jython 脚本从 Jython 移动到 GraalPy,请使用基于 GraalPy JVM 的运行时。(有关更多信息,请参阅可用的 GraalPy 分发版)。
导入 Java 包
Jython 的 Java 集成中有某些功能在 GraalPy 上默认启用。这里是一个例子:
>>> import java.awt as awt
>>> win = awt.Frame()
>>> win.setSize(200, 200)
>>> win.setTitle("Hello from Python!")
>>> win.getSize().toString()
'java.awt.Dimension[width=200,height=200]'
>>> win.show()
这个例子在 Jython 和 GraalPy 上运行时产生相同的结果。但是,当例子在 GraalPy 上运行时,只有 java
命名空间中的包可以直接导入。要从 java
命名空间之外的包导入类,请使用 --python.EmulateJython
选项。
注意:在模块化应用程序中嵌入 GraalPy 时,您可能需要根据 JSR 376 为所需模块添加导出。
此外,在所有情况下都不可能将 Java 包作为 Python 模块导入。例如,这将起作用:
import java.lang as lang
但是,这不会起作用:
import javax.swing as swing
from javax.swing import *
相反,直接导入其中一个类:
import javax.swing.Window as Window
基本对象使用
构造和使用 Java 对象和类是通过常规 Python 语法实现的。Java 对象的方法也可以作为一流对象(绑定到它们的实例)检索和引用,与 Python 方法相同。例如:
>>> from java.util import Random
>>> rg = Random(99)
>>> rg.nextInt()
1491444859
>>> boundNextInt = rg.nextInt
>>> boundNextInt()
1672896916
Java 到 Python 类型:自动转换
方法重载通过以最佳努力的方式将 Python 参数匹配到可用的参数类型来解决。这种方法在转换数据时也被采用。这里的目标是使从 Python 使用 Java 尽可能顺畅。GraalPy 采用的匹配方法类似于 Jython,但 GraalPy 使用更动态的匹配方法 — 模拟 int
或 float
的 Python 类型也会转换为适当的 Java 类型。例如,这使您可以将 Pandas 框架用作 double[][]
或将 NumPy 数组元素用作 int[]
,当元素适合这些 Java 原始类型时。
Java 类型 | Python 类型 |
---|---|
null | None |
boolean | bool |
byte , short , int , long | int ,任何有 __int__ 方法的对象 |
float | float ,任何有 __float__ 方法的对象 |
char | 长度为 1 的 str |
java.lang.String | str |
byte[] | bytes 、bytearray 、包装的 Java array 、只有适当类型的 Python 列表 |
Java 数组 | 包装的 Java array 或只有适当类型的 Python list |
Java 对象 | 适当类型的包装 Java 对象 |
java.lang.Object | 任何对象 |
特殊 Jython 模块:jarray
GraalPy 为了兼容性实现了 jarray
模块(创建原始 Java 数组)。这个模块总是可用的,因为我们发现它的存在没有负面影响。例如:
>>> import jarray
>>> jarray.array([1,2,3], 'i')
请注意,它的用法等同于使用 java.type
函数构造数组类型然后填充数组,如下所示:
>>> import java
>>> java.type("int[]")(10)
创建 Java 数组的代码也可以使用 Python 类型。但是,隐式地,这可能会产生数组数据的副本,当使用 Java 数组作为输出参数时这可能是欺骗性的:
>>> i = java.io.ByteArrayInputStream(b"foobar")
>>> buf = [0, 0, 0]
>>> i.read(buf) # buf 自动转换为 byte[] 数组
3
>>> buf
[0, 0, 0] # 转换后的 byte[] 数组丢失了
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]
来自 Java 的异常
您可以按预期捕获 Java 异常。例如:
>>> import java
>>> v = java.util.Vector()
>>> try:
... x = v.elementAt(7)
... except java.lang.ArrayIndexOutOfBoundsException as e:
... print(e.getMessage())
...
7 >= 0
Java 集合
-
Java 数组和实现
java.util.Collection
接口的集合可以使用[]
语法访问。空集合在布尔转换中被认为是false
。集合的长度由len
内置函数公开。例如:>>> from java.util import ArrayList >>> l = ArrayList() >>> l.add("foo") True >>> l.add("baz") True >>> l[0] 'foo' >>> l[1] = "bar" >>> del l[1] >>> len(l) 1 >>> bool(l) True >>> del l[0] >>> bool(l) False
-
实现
java.lang.Iterable
接口的 Java 可迭代对象可以使用for
循环或iter
内置函数迭代,并被所有期望可迭代对象的内置函数接受。例如:>>> [x for x in l] ['foo', 'bar'] >>> i = iter(l) >>> next(i) 'foo' >>> next(i) 'bar' >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> set(l) {'foo', 'bar'}
-
迭代器也可以被迭代。例如:
>>> from java.util import ArrayList >>> l = ArrayList() >>> l.add("foo") True >>> i = l.iterator() # 调用 Java 迭代器方法 >>> next(i) 'foo'
-
实现
java.util.Map
接口的映射集合可以使用[]
表示法访问。空映射在布尔转换中被认为是false
。映射的迭代产生其键,与dict
一致。例如:>>> from java.util import HashMap >>> m = HashMap() >>> m['foo'] = 5 >>> m['foo'] 5 >>> m['bar'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: bar >>> [k for k in m] ['foo'] >>> bool(m) True >>> del m['foo'] >>> bool(m) False
从 Java 继承
支持从 Java 类继承(或实现 Java 接口),但与 Jython 相比在语法和行为上有一些显著差异。要创建从 Java 类继承(或实现 Java 接口)的类,请使用常规的 Python class
语句。声明的方法在名称匹配时重写(实现)超类(接口)方法。
重要的是要理解这里实际上发生了委托 — 当从 Java 继承时,会创建两个类,一个在 Java 中,一个在 Python 中。它们相互引用,在 Python 中声明的任何重写或实现超类上 Java 方法的方法,在 Java 端被声明为委托给 Python。创建的对象不像 Python 对象那样行为,而是像外来的 Java 对象一样。这样做的原因是,当您创建新类的实例时,您得到的是对 Java 对象的引用。
要调用不重写或实现超类上已存在的方法的 Python 方法,您需要使用特殊的 this
属性。一旦您在 Python 方法中,您的 self
引用 Python 对象,要从 Python 方法返回到 Java,请使用特殊属性 __super__
。由于我们不在实例端公开静态成员,如果您需要从实例在 Java 端调用静态方法,请使用 getClass().static
来获取保存静态成员的元对象。
这里两对象模式的一个重要后果是 Python 对象上的 __init__
方法实际上在建立到 Java 端的连接之前被调用。因此,您目前无法重写 Java 对象的构造或在初始化期间运行会影响组合结构的 Java 一半的代码。如果您想实现这一点,您必须创建一个工厂方法。
例如:
import atexit
from java.util.logging import Logger, Handler
class MyHandler(Handler):
def __init__(self):
self.logged = []
def publish(self, record):
self.logged.append(record)
logger = Logger.getLogger("mylog")
logger.setUseParentHandlers(False)
handler = MyHandler()
logger.addHandler(handler)
# 确保在 Python 上下文关闭后不使用处理程序
atexit.register(lambda: logger.removeHandler(handler))
logger.info("Hi")
logger.warning("Bye")
# 对象的 python 属性/方法通过 'this' 属性访问
for record in handler.this.logged:
print(f'Python captured message "{record.getMessage()}" at level {record.getLevel().getName()}')
有关生成的 Java 子类如何行为的更多信息,请参阅 Truffle 文档。
将 Python 嵌入到 Java 中
使用 Jython 的另一种方式是将其嵌入到 Java 应用程序中。这种嵌入有两个选项。
- 使用 Jython 提供的
PythonInterpreter
对象。以这种方式使用 Jython 的现有代码直接依赖于 Jython 包(例如,在 Maven 配置中),因为 Java 代码引用了 Jython 内部类。这些类在 GraalVM 中不存在,也没有公开等效类。要从这种用法迁移,请切换到 GraalVM SDK。使用此 SDK,不会公开特定于 Python 的 API,一切都通过 GraalVM API 实现,具有 Python 运行时的最大可配置性。请参考 入门 文档来准备设置。 - 通过使用
javax.script
包的类,特别是通过ScriptEngine
类,通过 JSR 223 在 Java 中嵌入 Jython。我们不推荐这种方法,因为ScriptEngine
API 不适合 GraalPy 的选项和功能。但是,为了迁移现有代码,我们提供了一个示例 ScriptEngine 实现,您可以内联到您的项目中。请参考 嵌入的参考手册 获取详细信息。
互操作性
除了主要推荐在 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)
# 可以直接调用公共 Java 方法
myBigInt.shiftLeft(128) # 返回 <JavaObject[java.math.BigInteger] at ...>
# 在 Python 中是关键字的 Java 方法名必须使用 `getattr` 访问
getattr(myBigInt, "not")() # 返回 <JavaObject[java.math.BigInteger] at ...>
byteArray = myBigInt.toByteArray()
# Java 数组可以像 Python 列表一样操作
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 是使用互操作包装的 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 脚本与外部对象交互
外部对象被赋予对应于其互操作特征的 Python 类:
from java.util import ArrayList, HashMap
type(ArrayList()).mro() # => [<class 'polyglot.ForeignList'>, <class 'list'>, <class 'polyglot.ForeignObject'>, <class 'object'>]
type(HashMap()).mro() # => [<class 'polyglot.ForeignDict'>, <class 'dict'>, <class 'polyglot.ForeignObject'>, <class 'object'>]
这意味着这些类型的所有 Python 方法都可用于相应的外部对象,它们的行为尽可能接近 Python 对象:
from java.util import ArrayList, HashMap
l = ArrayList()
l.append(1) # l: [1]
l.extend([2, 3]) # l: [1, 2, 3]
l.add(4) # l: [1, 2, 3, 4] # 我们仍然可以调用 Java 方法,这是调用 ArrayList#add
l[1:3] # => [2, 3]
l.pop(1) # => 2; l: [1, 3, 4]
l.insert(1, 2) # l: [1, 2, 3, 4]
l == [1, 2, 3, 4] # True
h = HashMap()
h[1] = 2 # h: {1: 2}
h.setdefault(3, 4) # h: {1: 2, 3: 4}
h |= {3: 6} # {1: 2, 3: 6}
h == {1: 2, 3: 6} # True
如果方法在 Python 和外部对象上都有定义,Python 方法获胜。要调用外部方法,请使用 super(type_owning_the_python_method, foreign_object).method(*args)
:
from java.util import ArrayList
l = ArrayList()
l.extend([5, 6, 7])
l.remove(7) # Python list.remove
assert l == [5, 6]
super(list, l).remove(0) # ArrayList#remove(int index)
assert l == [6]
有关更多互操作特征以及它们如何映射到 Python 类型,请参阅此部分。
从 Python 脚本与其他动态语言交互
从 Python 脚本与其他语言的更一般的、非 JVM 特定的交互是通过 polyglot API 实现的。这包括与通过 Truffle 框架 支持的动态语言的所有交互,包括 JavaScript 和 Ruby。
安装其他动态语言
其他语言可以通过与 GraalPy 相同的方式使用各自的 Maven 依赖项来包含。例如,如果您已经配置了带有 GraalPy 的 Maven 项目,请添加以下依赖项以获得对 JavaScript 的访问:
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>25.0.0</version>
</dependency>
对于 Python 开发人员,其他语言仅在使用分发版根目录中的 libexec/graalpy-polyglot-get
命令后才可用于 GraalPy JVM 分发版。例如,要安装 JavaScript:
libexec/graalpy-polyglot-get js
示例
-
导入
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")
-
从 polyglot 作用域导入全局值:
Math = polyglot.import_value("JSMath")
这个全局值应该按预期工作:
-
访问属性从 polyglot 成员 命名空间读取:
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 类型 polyglot.ForeignObject
或子类。
下表中未列出的类型在 Python 中没有特殊解释。
互操作类型 | 继承自 | Python 解释 |
---|---|---|
null | ForeignNone, NoneType | null 类似于 None 。重要的是要知道:互操作 null 值都与 None 相同。JavaScript 定义了两个"类空"值;undefined 和 null ,它们不相同,但当传递给 Python 时,它们被这样对待。 |
boolean | ForeignBoolean, ForeignNumber | boolean 行为类似于 Python 布尔值,包括在 Python 中,所有布尔值也是整数(分别为 true 和 false 的 1 和 0)的事实。 |
number | ForeignNumber | number 行为类似于 Python 数字。Python 只有一种整数和一种浮点类型,但在某些地方(如类型化数组)会导入范围。 |
string | ForeignString, str | 行为与 Python 字符串相同。 |
buffer | ForeignObject | 缓冲区也是 Python 原生 API 中的概念(尽管略有不同)。互操作缓冲区在某些地方(如 memoryview )与 Python 缓冲区的处理方式相同,以避免数据拷贝。 |
array | ForeignList, list | array 行为类似于 Python list 。 |
hash | ForeignDict, dict | hash 行为类似于 Python dict ,使用任何"可哈希"对象作为键。"可哈希"遵循 Python 语义:通常每个具有身份的互操作类型都被认为是"可哈希的"。 |
members | ForeignObject | 类型为 members 的对象可以使用常规 Python . 表示法或 getattr 和相关函数读取。 |
iterable | ForeignIterable | iterable 的处理方式与任何具有 __iter__ 方法的 Python 对象相同。也就是说,它可以在循环和其他接受 Python 可迭代对象的地方使用。 |
iterator | ForeignIterator, iterator | iterator 的处理方式与任何具有 __next__ 方法的 Python 对象相同。 |
exception | ForeignException, BaseException | exception 可以在通用 except 子句中捕获。 |
MetaObject | ForeignAbstractClass | 元对象可以用于子类型和 isinstance 检查。 |
executable | ForeignExecutable | executable 对象可以作为函数执行,但永远不能使用关键字参数。 |
instantiable | ForeignInstantiable | instantiable 对象可以像 Python 类型一样调用,但永远不能使用关键字参数。 |
外部数字继承自 polyglot.ForeignNumber
而不是 int
或 float
,因为 InteropLibrary
目前无法区分整数和浮点数。但是:
- 当外部数字表示为 Java 原始类型
byte
、short
、int
、long
时,它们被认为是 Pythonint
对象。 - 当外部数字表示为 Java 原始类型
float
、double
时,它们被认为是 Pythonfloat
对象。 - 当外部布尔值表示为 Java 原始类型
boolean
时,它们被认为是 Pythonbool
对象。
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 对象。请注意,可读/可写的规则有点特殊,因为检查这不是 Python MOP 的一部分。 |
iterable | 任何具有 __iter__ 或 __getitem__ 方法的 Python 对象。 |
iterator | 任何具有 __next__ 方法的 Python 对象。 |
exception | 任何 Python BaseException 子类型。 |
MetaObject | 任何 Python type 。 |
executable | 任何具有 __call__ 方法的 Python 对象。 |
instantiable | 任何 Python type 。 |
Java 资源路径
特别是在开发可重用库时,建议为您的虚拟文件系统使用自定义唯一的 Java 资源路径,以避免与类路径或模块路径上可能也使用虚拟文件系统的其他库发生冲突。推荐的路径是:
GRAALPY-VFS/${project.groupId}/${project.artifactId}
Java 资源路径必须在 Maven 和 Gradle 插件中配置,并且必须在运行时使用 VirtualFileSystem$Builder#resourceDirectory
API 设置为相同的值。
关于 Java 模块系统的注意事项:命名模块中的资源受 Module.getResourceAsStream 指定的封装规则约束。默认虚拟文件系统位置也是如此。当资源目录不是有效的 Java 包名时,例如推荐的"GRAALPY-VFS",资源不受封装规则约束,不需要额外的模块系统配置。
从虚拟文件系统提取文件
通常,虚拟文件系统资源像 java 资源一样加载,但在某些情况下,文件需要在 Truffle 沙箱之外访问,例如需要由操作系统加载器访问的 Python C 扩展文件。
默认情况下,类型为 .so
、.dylib
、.pyd
、.dll
或 .ttf
的文件在首次访问时会自动提取到真实文件系统中的临时目录,然后虚拟文件系统委托给这些真实文件。
可以使用 VirtualFileSystem$Builder#extractFilter
API 增强默认提取规则。
或者,可以在创建 GraalPy 上下文之前将所有 Python 资源提取到用户定义的目录中,然后配置上下文使用该目录。请参考以下 GraalPyResources 方法了解更多详细信息:
GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem vfs, Path externalResourcesDirectory)
GraalPyResourcescontextBuilder(Path externalResourcesDirectory)
外部目录
作为使用虚拟文件系统的 Java 资源的替代方案,也可以配置 Maven 或 Gradle 插件来管理外部目录的内容,该目录不会作为 Java 资源嵌入到生成的应用程序中。然后用户负责部署此目录。Python 代码将直接从真实文件系统访问文件。
使用以下 GraalPyResources 工厂方法创建为使用外部目录而预配置的 GraalPy Context:
GraalPyResources.createContextBuilder(Path)
约定
GraalPyResources 中的工厂方法依赖于以下约定,其中 ${root}
要么是外部目录,要么是 Python 端的虚拟系统挂载点和真实文件系统上的 Java 资源目录,例如 ${project_resources_directory}/org.graalvm.python.vfs
:
${root}/src
:用于 Python 应用程序文件。此目录将配置为 Python 模块文件的默认搜索路径(相当于PYTHONPATH
环境变量)。${root}/venv
:用于包含已安装第三方 Python 包的 Python 虚拟环境。Context 将配置为如同从此虚拟环境执行。值得注意的是,安装在此虚拟环境中的包将自动可用于导入。
Maven 或 Gradle 插件将完全管理 venv
子目录的内容。任何手动更改将在构建期间被插件覆盖。
${root}/venv
:插件根据 pom.xml 或 build.gradle 中的插件配置创建虚拟环境并安装所需的包。
src 子目录留给用户手动填充自定义 Python 脚本或模块。
为了管理第三方 Python 包,在幕后使用 Python 虚拟环境。无论部署在虚拟文件系统还是外部目录中,其内容都由插件根据插件配置中指定的 Python 包进行管理。
用于可重现构建的 Python 依赖管理
在 Python 生态系统中,包通常将其依赖项指定为范围而不是固定版本。例如,包 A 依赖于任何版本高于或等于 2.0.0 的包 B(表示为 B>=2.0.0
)。今天安装包 A 可能会拉取包 B==2.0.0
。明天包 B 发布新版本 2.0.1
,依赖于 A 的项目的全新构建将拉取 B 的新版本,这可能与 GraalPy 不兼容或可能引入(无意的)破坏性更改。
锁定依赖项
我们强烈建议锁定所有 Python 依赖项,每当项目所需包列表发生更改时。锁定依赖项意味着显式调用 Maven 目标或 Gradle 任务,该任务生成文件 graalpy.lock
,该文件捕获所有 Python 包依赖项的版本:在 pom.xml
或 build.gradle
中显式指定的那些以及它们的所有传递依赖项。
graalpy.lock
文件应该提交到版本控制系统(例如,git)。一旦 graalpy.lock
文件存在,Maven 或 Gradle 构建期间的包安装将安装与 graalpy.lock
中捕获的完全相同的版本。
当 pom.xml
或 build.gradle
中的显式依赖项集发生更改并且不再与 graalpy.lock
中的内容匹配时,构建将失败,并要求用户显式重新生成 graalpy.lock
文件。
请注意,除非需要包的特定版本,否则我们建议在 pom.xml
或 build.gradle
中指定显式依赖项时不使用版本限定符。对于一些知名包,GraalPy 会自动安装已知与 GraalPy 兼容的版本。但是,一旦安装,版本应该被锁定以确保可重现的构建。
有关特定 Maven 或 Gradle 锁定包操作的信息,请参考下面的锁定 Python 包子部分。
GraalPy Maven 插件
Maven 插件配置
在 pom.xml 文件的 graalpy-maven-plugin
的 configuration
块中添加插件配置:
<plugin>
<groupId>org.graalvm.python</groupId>
<artifactId>graalpy-maven-plugin</artifactId>
...
<configuration>
...
</configuration>
...
</plugin>
-
packages 元素声明插件要下载和安装的第三方 Python 包列表。Python 包及其版本的指定方式与使用
pip
时相同:<configuration> <packages> <package>termcolor==2.2</package> ... </packages> ... </configuration>
-
resourceDirectory 元素可以指定相对的 Java 资源路径。记住在 Java 中配置
VirtualFileSystem
时使用VirtualFileSystem$Builder#resourceDirectory
。<resourceDirectory>GRAALPY-VFS/${project.groupId}/${project.artifactId}</resourceDirectory>
-
如果指定了 externalDirectory 元素,则给定目录用作 外部目录,不嵌入 Java 资源。记住使用适当的
GraalPyResources
API 创建 Context。此元素和 resourceDirectory 是互斥的。<configuration> <externalDirectory>${basedir}/python-resources</externalDirectory> ... </configuration>
-
如果您想要删除仅在 venv 创建期间需要但在运行时不需要的包,例如 setuptools 或 pip,您可以使用例如
maven-jar-plugin
:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.4.2</version> <configuration> <excludes> <exclude>**/site-packages/pip*/**</exclude> <exclude>**/site-packages/setuptools*/**</exclude> </excludes> </configuration> </plugin>
锁定 Python 包
要锁定指定 Python 包的依赖树,执行 GraalPy 插件目标 org.graalvm.python:graalpy-maven-plugin:lock-packages
。
$ mvn org.graalvm.python:graalpy-maven-plugin:lock-packages
请注意,该操作将覆盖现有的锁定文件。
有关此功能的高级描述,请参考本文档中的 用于可重现构建的 Python 依赖管理 部分。
-
graalPyLockFile 元素可以更改 GraalPy 锁定文件的默认路径。默认值是
${basedir}/graalpy.lock
。graalPyLockFile 元素本身不会触发锁定。锁定必须通过显式执行org.graalvm.python:graalpy-maven-plugin:lock-packages
目标来完成。<configuration> <graalPyLockFile>${basedir}/graalpy.lock</graalPyLockFile> ... </configuration>
GraalPy Gradle 插件
Gradle 插件配置
插件必须添加到 build.gradle 文件的 plugins 部分。version 属性定义要使用的 GraalPy 版本。
plugins {
// 其他插件 ...
id 'org.graalvm.python' version '25.0.0'
}
插件自动注入与插件版本相同版本的这些依赖项:
org.graalvm.python:python
org.graalvm.python:python-embedding
插件可以在 graalPy
块中配置:
-
packages 元素声明插件要下载和安装的第三方 Python 包列表。Python 包及其版本的指定方式与使用
pip
时相同。graalPy { packages = ["termcolor==2.2"] ... }
-
resourceDirectory 元素可以指定相对的 Java 资源路径。记住在 Java 中配置
VirtualFileSystem
时使用VirtualFileSystem$Builder#resourceDirectory
。resourceDirectory = "GRAALPY-VFS/my.group.id/artifact.id"
-
如果指定了 externalDirectory 元素,则给定目录用作 外部目录,不嵌入 Java 资源。记住使用适当的
GraalPyResources
API 创建 Context。graalPy { externalDirectory = file("$rootDir/python-resources") ... }
-
布尔标志 community 将自动注入的依赖项
org.graalvm.python:python
切换到社区构建:org.graalvm.python:python-community
。graalPy { community = true ... }
锁定 Python 包
要锁定指定 Python 包的依赖树,执行 GraalPy 插件任务 graalPyLockPackages
。
gradle graalPyLockPackages
请注意,该操作将覆盖现有的锁定文件。
有关此功能的高级描述,请参考本文档中的 用于可重现构建的 Python 依赖管理 部分。
- graalPyLockFile 元素可以更改 GraalPy 锁定文件的默认路径。默认值是
${basedir}/graalpy.lock
。graalPyLockFile 元素本身不会触发锁定。锁定必须通过显式执行graalPyLockPackages
任务来完成。 graalPy { graalPyLockFile = file("$rootDir/graalpy.lock") ... }
将 Visual Studio Code 与 GraalPy 一起使用
您可以在 Visual Studio (VS) Code 中使用 GraalPy 来创建虚拟环境、安装包以及开发和运行 Python 应用程序。
- 安装
graalpy
。(有关更多信息,请参阅 安装 GraalPy。) - 安装 VS Code 和 Python 扩展,请按照此处的说明操作:安装 Visual Studio Code 和 Python 扩展。
- 创建或打开 Python 文件。
- 为您的 Python 项目创建新的 venv 虚拟环境。(有关更多信息,请参阅 创建环境。)
- 按照 VS Code 说明安装包。(有关更多信息,请参阅 安装和使用包。)
- 使用 VS Code 菜单项运行您的 Python 应用程序。(有关更多信息,请参阅 运行 Hello World。)或者,使用 VS Code 终端模拟器运行
graalpy
命令。 - 您无法使用 VS Code 调试 Python 应用程序。相反,请打开 VS Code 终端模拟器并按照以下说明操作:调试 Python 应用程序。
GraalPy 故障排除
GraalPy 嵌入
VirtualFileSystem 无法加载文件
有些情况下,即使文件是虚拟文件系统资源的一部分,也无法加载文件。GraalPy 尝试通过自动提取一些已知文件到真实文件系统来防止这种情况,但如果您看到类似以下的错误:
ImportError: cannot load /graalpy_vfs/venv/lib/python3.11/site-packages/_cffi_backend.graalpy250dev09433ef706-311-native-aarch64-darwin.so:
NFIUnsatisfiedLinkError: dlopen(/graalpy_vfs/venv/lib/python3.11/site-packages/_cffi_backend.graalpy250dev09433ef706-311-native-aarch64-darwin.so, 0x0002):
那么默认行为没有按预期工作。
根据您在应用程序中如何部署 Python 资源,您可以尝试以下方法之一:
-
如果您需要在 Jar 或 Native Image 可执行文件中打包资源:
- 如果有问题的文件不是以下类型之一:
.so
、.dylib
、.pyd
、.dll
或.ttf
(默认情况下这些类型会提取到真实文件系统),您可以简单地尝试使用以下方法将其添加到提取过滤器:
VirtualFileSystem.Builder.extractFilter(filter);
- 如果前面的方法没有帮助,也可以在创建 GraalPy 上下文之前将所有 Python 资源提取到用户定义的目录中,然后配置上下文使用该目录:
// 将 Python 资源从 jar 或 native image 提取到给定目录 GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath); // 创建配置了外部 Python 资源目录的 GraalPy 上下文 Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();
- 如果有问题的文件不是以下类型之一:
-
或者如果您能够在单独的目录中提供资源,您必须在 Maven 中设置
externalDirectory
标签或在 Gradle 中设置externalDirectory
字段,并且还要配置 GraalPy 上下文也使用该目录:// 创建配置了外部 Python 资源目录的 Context Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();
请注意,如果从虚拟文件系统切换到外部目录,还必须将以前虚拟文件系统资源根的所有用户文件也移动到该目录中。
有关 GraalPy 嵌入中 Python 资源的更多详细信息,请参考 嵌入构建工具 文档。
有关 GraalPy 上下文和虚拟文件系统配置的更多详细信息,请参考 GraalPyResources 和 VirtualFileSystem javadoc。
GraalPy 'java' posix 后端的问题
虚拟文件系统构建在 Truffle 文件系统上,依赖于 GraalPy Java POSIX 后端。不幸的是,某些 Python 包绕过 Python 的 I/O 并通过其原生扩展直接访问文件。如果您遇到类似以下的错误:
NotImplementedError: 'PyObject_AsFileDescriptor' not supported when using 'java' posix backend
那么您必须覆盖默认的 java
GraalPy 后端选项,通过设置 native
POSIX 后端并在运行时完全省略虚拟文件系统。
根据您在应用程序中如何部署 Python 资源,您可以执行以下操作之一:
-
如果您需要在 Jar 或 Native Image 可执行文件中打包 Python 资源,则:
// 将 Python 资源从 jar 或 native image 提取到给定目录 GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath); // 创建配置了外部 python 资源目录的 Context.Builder Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath); // 用"native"覆盖 python.PosixModuleBackend 选项 builder.option("python.PosixModuleBackend", "native"); // 创建上下文 Context context = builder.build();
-
或者如果您能够在单独的目录中提供 Python 资源,您必须在 Maven 中设置
externalDirectory
标签或在 Gradle 中设置externalDirectory
字段,并相应地配置 GraalPy 上下文:// 创建配置了外部 python 资源目录的 Context.Builder Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath); // 用"native"覆盖 python.PosixModuleBackend 选项 builder.option("python.PosixModuleBackend", "native"); // 创建上下文 Context context = builder.build();
请注意,如果从虚拟文件系统切换到外部目录,还必须将以前虚拟文件系统资源根的所有用户文件也移动到该目录中。
有关 GraalPy 嵌入中 Python 资源的更多详细信息,请参考 嵌入构建工具 文档。
有关 GraalPy 上下文和虚拟文件系统配置的更多详细信息,请参考 GraalPyResources 和 VirtualFileSystem javadoc。
ImportError 报告"unknown location"
运行 GraalPy Java 嵌入项目时以 (unknown location)
结尾的 ImportError
的可能原因可能是嵌入的 Python 包是为不同的操作系统构建的。如果您看到类似以下的错误:
ImportError: cannot import name 'exceptions' from 'cryptography.hazmat.bindings._rust' (unknown location)
您可能需要在正确的操作系统上重新构建项目,然后再运行它。
GraalVM JDK 兼容性
要在从 Maven 或 Gradle 应用程序运行 GraalPy 时启用运行时编译,您必须使用与指定 GraalVM JDK 版本兼容的 GraalPy 和 Polyglot API 依赖项版本。如果您看到类似以下的错误:
Your Java runtime '23.0.1+11-jvmci-b01' with compiler version '24.1.1' is incompatible with polyglot version '24.1.0'.
您需要根据错误消息保持 GraalPy 和 Polyglot 依赖项的版本,因此要么升级 JDK 版本,要么升级 polyglot 和 GraalPy 依赖项。
注意,当您的依赖项被另一个工件传递性拉入时,这也可能适用,例如 micronaut-graalpy。
无法解析以下工件:org.graalvm.python:python-language-enterprise
python-language-enterprise
已停用,请使用 org.graalvm.polyglot:python
代替。
在 OSX 上使用 Maven 或 Gradle GraalPy 插件安装 Python 包时 Meson 构建系统的问题
类似以下的错误:
../meson.build:1:0: ERROR: Failed running 'cython', binary or interpreter not executable.
可能是由 Maven 或 Gradle GraalPy 插件内部用于安装 Python 包的 GraalPy 启动器引起的。目前,没有直接的解决方案。但是,一个解决方法是将 Java 系统属性 graalpy.vfs.venvLauncher 设置为从与 GraalPy Maven 工件版本匹配的已下载 GraalPy 分发版中的启动器。
例如:
mvn package -Dgraalpy.vfs.venvLauncher={graalpy_directroy}/Contents/Home/bin/graalpy
在模块路径上找不到语言和 polyglot 实现。
如果您看到类似以下的错误:
java.lang.IllegalStateException: No language and polyglot implementation was found on the module-path. Make sure at last one language is added to the module-path.
您可能在 Maven 或 Gradle 配置文件中缺少 python 语言依赖项。您需要添加 org.graalvm.polyglot:python
或 org.graalvm.polyglot:python-community
到您的依赖项中。