在 JVM 上开始使用 GraalPy

您可以将 GraalPy 与 GraalVM JDK、Oracle JDK 或 OpenJDK 结合使用。如以下所示,您可以使用 Maven 或 Gradle 构建工具轻松地将 GraalPy 添加到您的 Java 应用程序中。其他构建系统(如 Ant、Make、CMake 等)也可以通过少量手动工作来使用。

Maven

GraalPy 可以生成一个 Maven 项目,该项目使用 Maven 工件将 Python 包嵌入到 Java 应用程序中。

  1. GraalPy 项目发布了一个 Maven 原型来生成一个入门项目
    mvn archetype:generate \
      -DarchetypeGroupId=org.graalvm.python \
      -DarchetypeArtifactId=graalpy-archetype-polyglot-app \
      -DarchetypeVersion=24.2.0
    
  2. 使用已自动为您添加的 GraalVM Native Image “工具” 插件构建原生可执行文件
     mvn -Pnative package
    
  3. 完成后,运行可执行文件
     ./target/polyglot_app
    

    应用程序会在控制台打印“hello java”。

该项目使用 GraalVM Polyglot API,并增加了管理 Python 虚拟环境以及将 Python 包依赖项与 Maven 工作流集成等功能。Java 代码和 pom.xml 文件都有详细的文档,并且生成的代码描述了可用功能。

另请参阅 嵌入式构建工具,了解有关 GraalPy Maven 插件的更多信息。

创建带原生 Python 包的跨平台 JAR

生成项目使用 GraalPy Maven 插件,这使得添加 Python 依赖项变得容易。然而,Python 包可能包含特定于构建系统的原生组件。为了在其他系统上分发生成的应用程序,请按照以下步骤操作:

  1. 在每个部署平台上构建项目。重命名 JAR 文件,使其各自具有平台特定的名称,并将其移动到同一台机器上的临时目录中。

  2. 解压缩每个 JAR 文件(替换为 JAR 文件的正确名称)。一个特殊文件 vfs/fileslist.txt 需要从每个 JAR 文件中拼接。最后,从所有文件的组合以及拼接后的 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

  1. 使用以下命令创建 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
    
  2. 打开您的项目配置文件 app/build.gradle,并按如下修改。
    • dependencies 部分包含 GraalPy 支持和 GraalVM Polyglot API
        implementation("org.graalvm.polyglot:polyglot:24.2.0")
        implementation("org.graalvm.polyglot:python:24.2.0")
      
  3. 最后,将 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());
             }
         }
     }
    
  4. 使用 Gradle 运行应用程序
     ./gradlew run
    

    应用程序会在控制台打印“Hello Python!”。

    注意:GraalPy 运行时的性能取决于您嵌入它的 JDK。有关更多信息,请参阅 运行时优化支持

  5. (可选)您还可以使用第三方 Python 包。

    5.1. 在 app/build.gradle

    • 将 graalpy-gradle-plugin 添加到 plugins 部分
      id "org.graalvm.python" version "24.2.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 插件的更多信息。

不直接支持 Maven 依赖项的 Ant、CMake、Makefile 或其他构建系统

一些(通常是较旧的)项目可能使用 Ant、Makefiles、CMake 或其他不直接支持 Maven 依赖项的构建系统。像 Apache Ivy™ 这样的项目可以使这些构建系统解析 Maven 依赖项,但开发者可能出于某些原因不使用它们。GraalPy 提供了一个工具来从 Maven 获取所需的 JAR 文件。

  1. 假设有一个目录用于存储项目中的第三方依赖项,并且构建系统已配置为将所有 JAR 文件放入该目录并添加到类路径中,则项目目录树可能类似于:

     ├───lib
     │   └─── ... *.jar dependencies are here
     └───src
         └─── ... *.java files and resources are here
    
  2. 为您的系统安装 GraalPy 并确保 graalpy 在您的 PATH 中。打开命令行界面并进入您的项目目录。然后,根据您的系统,运行以下命令之一:

    在 POSIX Shell 中

     export GRAALPY_HOME=$(graalpy -c 'print(__graalpython__.home)')
     "${GRAALPY_HOME}/libexec/graalpy-polyglot-get" -a python -o lib -v "24.2.0"
    

    在 PowerShell 中

     $GRAALPY_HOME = graalpy -c "print(__graalpython__.home)"
     & "$GRAALPY_HOME/libexec/graalpy-polyglot-get" -a python -o lib -v "24.2.0"
    

    这些命令将所有 GraalPy 依赖项下载到 lib 目录中。

  3. 只要您的构建系统已配置为从 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 构建的 GraalPyGraalPy Community 提供。

  • 基于 Oracle GraalVM 构建的 GraalPy 提供最佳体验:它带有额外的优化,速度显著更快,内存效率更高。它根据GraalVM 免费条款和条件 (GFTC) 许可证授权,与 Oracle GraalVM 相同,允许任何用户使用,包括商业和生产用途。只要不收费,就允许再分发。

  • GraalPy Community 基于 GraalVM Community Edition 构建,并且是完全开源的。

GraalPy 的 Oracle 和 Community 分发版提供两种语言运行时选项:

  • 原生
    • GraalPy 是提前编译成原生可执行文件的。
    • 这意味着您不需要 JVM 来运行 GraalPy,并且它体积小巧。
  • JVM
    • 您可以轻松利用 Java 互操作性。
    • 峰值性能可能高于原生选项。

GraalPy 识别

四种 GraalPy 运行时按照以下通用模式进行识别:graalpy(-community)(-jvm)-<version>-<os>-<arch>

  Oracle 社区
原生 graalpy-<version>-<os>-<arch> graalpy-community-<version>-<os>-<arch>
JVM graalpy-jvm-<version>-<os>-<arch> graalpy-community-jvm-<version>-<os>-<arch>

比较

运行时 原生(默认) JVM
启动时间 更快 更慢
达到峰值性能的时间 更快 更慢
峰值性能(也考虑 GC) 最佳
Java 互操作性 需要配置 可用

安装 GraalPy

注意:GraalPy 发布后,其在 Pyenv 上的可用性会存在延迟。

Linux

在 Linux 上安装 GraalPy 最简单的方法是使用 Pyenv(Python 版本管理器)。要使用 Pyenv 安装 24.2.0 版,请运行以下命令:

pyenv install graalpy-24.2.0
pyenv shell graalpy-24.2.0

在运行 pyenv install 之前,您可能需要更新 pyenv 以包含最新的 GraalPy 版本。

或者,您可以从 GitHub 发布页面下载压缩的 GraalPy 安装文件。

  1. 找到与模式 graalpy-XX.Y.Z-linux-amd64.tar.gzgraalpy-XX.Y.Z-linux-aarch64.tar.gz 匹配的下载文件(取决于您的平台)并下载。
  2. 解压缩文件并更新您的 PATH 环境变量,使其包含 graalpy-XX.Y.Z-linux-amd64/bin(或 graalpy-XX.Y.Z-linux-aarch64/bin)目录。

macOS

在 macOS 上安装 GraalPy 最简单的方法是使用 Pyenv(Python 版本管理器)。要使用 Pyenv 安装 24.2.0 版,请运行以下命令:

pyenv install graalpy-24.2.0
pyenv shell graalpy-24.2.0

在运行 pyenv install 之前,您可能需要更新 pyenv 以包含最新的 GraalPy 版本。

或者,您可以从 GitHub 发布页面下载压缩的 GraalPy 安装文件。

  1. 找到与模式 graalpy-XX.Y.Z-macos-amd64.tar.gzgraalpy-XX.Y.Z-macos-aarch64.tar.gz 匹配的下载文件(取决于您的平台)并下载。
  2. 删除隔离属性。
     sudo xattr -r -d com.apple.quarantine /path/to/graalpy
    

    例如

     sudo xattr -r -d com.apple.quarantine ~/.pyenv/versions/graalpy-24.2.0
    
  3. 解压缩文件并更新您的 PATH 环境变量,使其包含 graalpy-XX.Y.Z-macos-amd64/bin(或 graalpy-XX.Y.Z-macos-aarch64/bin)目录。

Windows

  1. GitHub 发布页面查找并下载与模式 graalpy-XX.Y.Z-windows-amd64.tar.gz 匹配的压缩 GraalPy 安装文件。
  2. 解压缩文件并更新您的 PATH 环境变量,使其包含 graalpy-XX.Y.Z-windows-amd64/bin 目录。
Windows 限制

GraalPy 的 Windows 分发版比其 Linux 或 macOS 版本有更多限制,因此并非所有功能和包都可用。

它有以下已知问题:

  • JLine 将 Windows 视为哑终端,在 REPL 中没有自动补全和有限的编辑功能
  • REPL 中的交互式 help() 不起作用
  • 在虚拟环境中
    • graalpy.cmdgraalpy.exe 已损坏
    • pip.exe 无法直接使用
    • pip 在加载缓存文件时有问题,请使用 --no-cache-dir
    • 只能安装纯 Python 二进制 wheel,不能安装原生扩展或源代码构建
    • 要安装包,请使用 myvenv/Scripts/python.exe -m pip --no-cache-dir install <pkg>
  • 从 PowerShell 运行比从 CMD 运行效果更好,后者会导致各种脚本失败

安装包

使用 GraalPy 的最佳方式是使用 venv 虚拟环境。这会生成包装脚本,并使该实现可以从 shell 中作为标准 Python 解释器使用。

  1. 通过运行以下命令使用 GraalPy 创建虚拟环境:
     graalpy -m venv <venv-dir>
    

    例如

     graalpy -m venv ~/.virtualenvs/graalpy-24.2.0
    
  2. 在您的 shell 会话中激活环境
     source <venv-dir>/bin/activate
    

    例如

     source ~/.virtualenvs/graalpy-24.2.0/bin/activate
    

虚拟环境中提供多个可执行文件,包括:pythonpython3graalpy

注意:要停用 Python 环境(并返回到您的 shell),请运行 deactivate

使用虚拟环境时,pip 包安装程序可用。GraalPy 对 pip 的实现可能会选择除最新版本之外的包版本,以便在提供补丁时使其更好地工作。

Python 性能

执行性能

GraalPy 使用 GraalVM 最先进的即时 (JIT) 编译器。在 JIT 编译时,GraalPy 在官方 Python 性能基准套件上运行 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 文件

当存在与相应 .py 文件匹配的无效或缺失 .pyc 文件时,GraalPy 会自动创建一个 .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 - 等同于 -BPYTHONDONTWRITEBYTECODE
  • 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 版(现已结束生命周期),Jython 是连接 Python 和 Java 的事实标准。大多数使用 Java 集成的现有 Jython 代码都将基于稳定的 Jython 版本——但是,这些版本仅在 Python 2.x 中可用。相比之下,GraalPy 与 Python 3.x 兼容,并且不提供与早期 Jython 2.x 版本的完全兼容性。

要将代码从 Python 2 迁移到 Python 3,请遵循Python 社区的官方指南。一旦您的 Jython 代码与 Python 3 兼容,请遵循本指南以解决 GraalPy 和 Jython 之间的其他差异。

GraalPy 对 Java 互操作性的一流支持使得从 Python 使用 Java 库尽可能简单,并且除了对其他 Graal 语言(在 Truffle 框架上实现的语言)的通用互操作性支持之外,还为 Java 代码提供了特殊便利。

GraalPy 不支持 Jython 的所有功能。有些功能受支持,但由于对运行时性能有负面影响而默认禁用。在迁移期间,您可以使用命令行选项 --python.EmulateJython 启用这些功能。但是,我们建议您放弃这些功能,以实现最佳性能。

迁移 Jython 脚本

要将普通的 Jython 脚本从 Jython 迁移到 GraalPy,请使用基于 JVM 的 GraalPy 运行时。(有关更多信息,请参阅可用的GraalPy 分发版)。

导入 Java 包

GraalPy 默认启用 Jython 的 Java 集成的一些特定功能。例如:

>>> 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

基本对象使用

使用常规 Python 语法即可构造和使用 Java 对象和类。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 使用更动态的匹配方法——模拟 intfloat 的 Python 类型也会转换为适当的 Java 类型。例如,当元素适合这些 Java 基本类型时,这使您能够将 Pandas 框架用作 double[][] 或将 NumPy 数组元素用作 int[]

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 list
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 is automatically converted to a byte[] array
3
>>> buf
[0, 0, 0] # the converted byte[] array is lost
>>> 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.util.Collection 接口的 Java 数组和集合可以使用 [] 语法访问。空集合在布尔转换中被认为是 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()  # Calls the Java iterator methods
      >>> 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)
# Make sure the handler is not used after the Python context has been closed
atexit.register(lambda: logger.removeHandler(handler))

logger.info("Hi")
logger.warning("Bye")

# The python attributes/methods of the object are accessed through 'this' attribute
for record in handler.this.logged:
    print(f'Python captured message "{record.getMessage()}" at level {record.getLevel().getName()}')

有关生成的 Java 子类行为的更多信息,请参阅 Truffle 文档

将 Python 嵌入 Java

另一种使用 Jython 的方法是将其嵌入到 Java 应用程序中。这种嵌入有两种选择。

  1. 使用 Jython 提供的 PythonInterpreter 对象。以这种方式使用 Jython 的现有代码直接依赖于 Jython 包(例如,在 Maven 配置中),因为 Java 代码引用了 Jython 内部类。这些类在 GraalVM 中不存在,也没有暴露等效的类。要从这种用法迁移,请切换到 GraalVM SDK。使用此 SDK,没有特定于 Python 的 API 暴露,所有功能都通过 GraalVM API 实现,并具有 Python 运行时的最大可配置性。有关准备设置的信息,请参阅入门文档。

  2. 通过使用 javax.script 包中的类,特别是通过 ScriptEngine 类,将 Jython 嵌入 Java 中。我们不推荐这种方法,因为 ScriptEngine API 与 GraalPy 的选项和功能不完全匹配。但是,为了迁移现有代码,我们提供了一个 ScriptEngine 实现示例,您可以将其内联到您的项目中。有关详细信息,请参阅嵌入参考手册

带 Python 的原生可执行文件

GraalPy 支持 GraalVM Native Image,以生成使用 GraalPy 的 Java 应用程序的原生二进制文件。

快速入门

如果您从Maven 原型开始,生成的 pom.xml 可以轻松地使用 用于 Native Image 构建的 Maven 插件生成原生可执行文件。

要构建应用程序,请运行:

mvn -Pnative package

该命令会打包项目并创建原生可执行文件。

查看生成的 pom.xmlMain.java 文件。它们都附有文档,解释了 Python 资源如何包含在生成的二进制文件中。生成的项目应被视为一个起点。它包含了整个 Python 标准库,因此 Python 代码可以调用所有标准库代码。资源可以手动精简,以将包含的 Python 库减少到所需数量,从而减小包的大小并缩短启动时间。此 Java 示例演示了 Python 上下文的一些有用默认选项,但可能需要其他设置以进一步控制 Python 代码允许执行的操作。

减小二进制文件大小

Python 是一种大型语言。“包含电池”一直是 CPython 的核心原则。作为兼容的替代品,GraalPy 也包含了这些“电池”中的大部分。这可能导致在 Java 应用程序中包含 GraalPy 时,二进制文件大小显著增加。

只有您作为开发者了解您特定的嵌入场景。默认设置可能包含任何特定应用程序所需的多得多的内容。嵌入式 Java-Python 应用程序通常对 Python 解释器的使用场景比完整的 GraalPy 分发版更有限,而且您通常可以提前知道是否需要某些功能。某些功能(例如,加密算法或套接字访问)在某些情况下甚至可能不希望包含。因此,在将 GraalPy 嵌入到 Java 应用程序中时,可以通过排除 Python 语言的组件来减小二进制文件大小并提高安全性。

排除 Python 组件

GraalPy 定义了一些系统属性,可以在 native-image 命令行上传递这些属性,以排除语言的某些方面。这些选项结合起来,可以将可执行文件的大小减少约 20%。这些选项是:

  • python.WithoutSSL=true - 此选项会移除 ssl 模块。如果不需要安全网络访问或证书检查,这将移除 Java 的 SSL 类和 BouncyCastle 库。
  • python.WithoutDigest=true - 此选项会移除 _md5_sha1_sha256_sha512_sha3_hashlib 模块。这会从 GraalPy 中移除对 java.security.MessageDigestjavax.crypto.Mac 的直接使用。
  • python.WithoutPlatformAccess=true - 这会移除 signalsubprocess 模块,移除对 Unix UID 和 GID 等进程属性的访问,或设置 Java 默认时区。这对二进制文件大小没有显著影响,但如果这些是不需要的功能,并且无论如何都会通过上下文选项动态禁用,那么也可以提前移除它们。
  • python.WithoutCompressionLibraries=true - 这会移除 zliblzmabzip2zipimporter 模块以及相关类。这些模块既有原生实现,也有纯 Java 实现(前者用于性能,后者用于更好的沙箱环境);但是,如果不需要它们,则可以完全移除。
  • python.WithoutNativePosix=true - 当 GraalPy 被嵌入而非通过其启动器运行时,默认的 os 模块后端是纯 Java 实现。GraalPy 的原生 POSIX 后端仅建议用于与 CPython 的 POSIX 接口实现 100% 兼容,如果不需要,则可以使用此选项从构建中移除。
  • python.WithoutJavaInet=true - Python 的 socket 模块的 Java 实现基于 Java 的网络类。如果在嵌入场景中拒绝网络访问,此选项可以进一步减小二进制文件大小。
  • python.AutomaticAsyncActions=false - 信号处理、Python 弱引用回调以及清理原生资源通常通过生成 GraalPy 守护线程自动实现,这些线程将安全点操作提交到 Python 主线程。这使用带有线程池的 ExecutorService。如果您想禁止此类额外线程或避免引入 ExecutorService 和相关类,则将此属性设置为 false,并从上下文的 polyglot 绑定中检索 PollPythonAsyncActions 对象。此对象是可执行的,可用于在您想要的位置触发 Python 异步操作。

移除预初始化的 Python 堆

另一个有助于减小原生可执行文件大小的有用选项是从可执行文件中省略预初始化的 Python 上下文。默认情况下,默认的 Python 上下文已预先初始化并准备好立即执行。这可能会稍微改善启动速度,但代价是在二进制文件中包含数千个 Python 对象。在使用自定义多语言引擎允许上下文共享的嵌入式应用程序中,预初始化的上下文根本无法使用,并且包含这些对象是浪费的。可以通过将以下内容传递给 native-image 命令来省略预初始化堆:

-Dimage-build-time.PreinitializeContexts=

禁用 Python 代码的运行时编译

如果二进制文件大小比执行速度重要得多(当所有 Python 脚本预计运行时间短、执行大量 I/O 或脚本很少执行一次以上时,可能会出现这种情况),则完全禁用 JIT 编译可能是有意义的。请注意,这可能会显著影响您的 Python 性能,因此在选择使用此选项时,请务必测试实际用例的运行时行为。可以通过传递以下选项来实现:

-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime \
-Dpolyglot.engine.WarnInterpreterOnly=false

总结

结合所有这些方法可以将 GraalPy 二进制文件的大小减半。每个嵌入式应用程序都不同,并且其余 Java 代码引入的代码也很重要,因此应尝试这些选项的组合,以确定它们在特定实例中的效果。

交付 Python 包

我们的 Maven 原型默认设置为将所有必需的 Python 文件包含在原生二进制文件中,因此镜像可以自包含。

在自定义嵌入中,Python 标准库会复制到原生镜像旁边。移动原生镜像时,标准库文件夹需要保持在旁边。

Python 独立应用程序

GraalPy 使您能够将 Python 应用程序或库创建为不带外部依赖项的原生应用程序或 JAR 文件。GraalPy 构建于其上的 Truffle 框架虚拟化了所有文件系统访问,包括对标准库和已安装的纯 Python 包的访问。但是,包含原生代码的包仍然可以规避这一点!

GraalPy 包含一个名为 standalone 的模块,用于为 Linux、macOS 和 Windows 创建 Python 二进制文件。该模块将您的应用程序的所有资源捆绑到一个文件中。

前提条件:GraalPy 分发版版本 23.1.0 或更高。请参阅 GraalPy 版本

例如,如果您想从名为 my_script.py 的 Python 文件以及您在名为 my_venv 的虚拟环境中安装的包生成原生可执行文件,请运行以下命令:

graalpy -m standalone native \
      --module my_script.py \
      --output my_binary \
      --venv my_env

它会生成一个原生 my_binary 文件,该文件将您的 Python 代码、GraalPy 运行时和 Python 标准库包含在一个单独的自包含可执行文件中。使用 graalpy -m standalone native --help 获取更多选项。

Python 独立应用程序的安全考虑

创建包含 Python 代码的原生可执行文件或 JAR 文件可以被视为一种轻微的混淆形式,但它不能保护您的源代码。Python 源代码并非逐字存储到可执行文件中(只存储 GraalPy 字节码),但字节码很容易转换回 Python 源代码。

互操作性

除了主要推荐在 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 的实例(class 必须是外部对象类),则返回 True
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] # we can still call Java methods, this is calling 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>24.2.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. 从多语言作用域导入全局值
    Math = polyglot.import_value("JSMath")
    

    这个全局值应该会按预期工作

    • 访问属性会从 多语言成员 命名空间读取
      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 类型 polyglot.ForeignObject 或其子类。

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

互操作类型 继承自 Python 解释
null ForeignNone, NoneType null 类似于 None。重要提示:互操作 null 值都与 None 相同。JavaScript 定义了两个“类似 null”的值:undefinednull,它们 相同,但当传递给 Python 时,它们被视为相同。
boolean ForeignBoolean, ForeignNumber boolean 的行为类似于 Python 布尔值,包括在 Python 中所有布尔值也都是整数(True 为 1,False 为 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 而不是 intfloat,因为 InteropLibrary 目前无法区分整数和浮点数。然而:

  • 当外部数字表示为 Java 基本类型 byteshortintlong 时,它们被认为是 Python int 对象。
  • 当外部数字表示为 Java 基本类型 floatdouble 时,它们被认为是 Python float 对象。
  • 当外部布尔值表示为 Java 基本类型 boolean 时,它们被认为是 Python bool 对象。

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 将接收者 type 作为第一个参数。其余的关键字参数对应于各自的互操作消息。并非所有互操作消息都受支持。
get_registered_interop_behavior 将接收者 type 作为第一个参数。返回给定类型的扩展互操作消息列表。
@interop_behavior 类装饰器,仅将接收者 type 作为参数。互操作消息通过装饰类(供应商)中定义的 静态 方法进行扩展。
register_interop_type foreign classpython class 作为位置参数,并将 allow_method_overwrites 作为可选参数(默认:False)。外部类的每个实例随后都被视为给定 python 类的实例。
@interop_type 类装饰器,将 foreign class 和可选的 allow_method_overwrites 作为参数。外部类的实例将被视为带注解的 python 类的实例。

使用示例

互操作行为

提供了一个简单的 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;
        }
    }
}
互操作类型

register_interop_type API 允许将 python 类用于外部对象。此类外部对象的类将不再是 polyglot.ForeignObjectpolyglot.Foreign*。相反,它将是一个生成的类,其中包含已注册的 python 类和 polyglot.ForeignObject 作为超类。这允许将外部方法和属性自定义映射到 Python 的魔术方法或更符合 Python 习惯的代码。

package org.example;

class MyJavaClass {
      private int x;
      private int y;
      
      public MyJavaClass(int x, int y) {
         this.x = x;
         this.y = y;
      }

      public int getX() {
         return x;
      }

      public int getY() {
         return y;
      }
   }
import org.example.MyJavaClass;
        
class Main {

   public static void main(String[] args) {
      MyJavaClass myJavaObject = new MyJavaClass(42, 17);
      try (var context = Context.create()) {
         // myJavaObject will be globally available in example.py as my_java_object
         context.getBindings("python").putMember("my_java_object", myJavaObject);
         context.eval(Source.newBuilder("python", "example.py"));
      }
   }
}
# example.py
import java
from polyglot import register_interop_type

print(my_java_object.getX()) # 42
print(type(my_java_object)) # <class 'polyglot.ForeignObject'>

class MyPythonClass:
   def get_tuple(self):
      return (self.getX(), self.getY())

foreign_class = java.type("org.example.MyJavaClass")

register_interop_type(foreign_class, MyPythonClass)

print(my_java_object.get_tuple()) # (42, 17)
print(type(my_java_object)) # <class 'polyglot.Java_org.example.MyJavaClass_generated'>
print(type(my_java_object).mro()) # [polyglot.Java_org.example.MyJavaClass_generated, MyPythonClass, polyglot.ForeignObject, object]

class MyPythonClassTwo:
   def get_tuple(self):
      return (self.getY(), self.getX())
   
   def __str__(self):
      return f"MyJavaInstance(x={self.getX()}, y={self.getY()}"

# If 'allow_method_overwrites=True' is not given, this would lead to an error due to the method conflict of 'get_tuple'  
register_interop_type(foreign_class, MyPythonClassTwo, allow_method_overwrites=True)

# A newly registered class will be before already registered classes in the mro.
# It allows overwriting methods from already registered classes with the flag 'allow_method_overwrites=True'
print(type(my_java_object).mro()) # [generated_class, MyPythonClassTwo, MyPythonClass, polyglot.ForeignObject, object]

print(my_java_object.get_tuple()) # (17, 42)
print(my_java_object) # MyJavaInstance(x=42, y=17)

使用 @interop_type 注册类可能更方便:

import java
from polyglot import interop_type

foreign_class = java.type("org.example.MyJavaClass")

@interop_type(foreign_class)
class MyPythonClass:
   def get_tuple(self):
      return (self.getX(), self.getY())

支持的消息

大多数互操作消息(除少数例外)都受到互操作行为扩展 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 字符串(时区)或整数(UTC 秒数差)
execute execute 对象
readArrayElement read_array_element 对象
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 对象
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 对象
writeHashEntry write_hash_entry NoneType
removeHashEntry remove_hash_entry NoneType

嵌入式构建工具

GraalPy MavenGradle 插件提供了管理与 Python 相关的资源的功能,这些资源是在基于 Java 的应用程序中嵌入 Python 代码所需的

  • 用户提供的 Python 应用程序文件,例如作为项目一部分的 Python 源代码。
  • 插件在构建期间根据插件配置安装的 第三方 Python 包

除了物理管理和部署这些文件之外,还需要通过相应地配置 Java 代码中的 GraalPy 上下文,使其在 Python 运行时可用。GraalPyResources API 提供了工厂方法,用于创建预配置的上下文,以访问 Python,并将相关资源嵌入到 虚拟文件系统 或从专门的 外部目录 中。

部署

有两种部署资源模式:作为 Java 资源使用虚拟文件系统在 Python 中访问它们,或者作为外部目录。

虚拟文件系统

与 Python 相关的资源以标准 Java 资源的形式嵌入到应用程序文件中,无论是 JAR 还是 Native Image 生成的可执行文件。GraalPy 虚拟文件系统在内部将资源文件作为标准 Java 资源访问,并使其可用于在 GraalPy 中运行的 Python 代码。这对于 Python 代码是透明的,Python 代码可以使用标准 Python IO 来访问这些文件。

Maven 或 Gradle 项目中的 Java 资源文件通常位于专用的资源目录中,例如 src/main/resources。此外,可以有多个资源目录,Maven 或 Gradle 通常会将它们合并。

用户可以选择将通过虚拟文件系统在 Python 中可访问的相对 Java 资源路径,默认情况下是 org.graalvm.python.vfs。所有具有此路径的资源子目录在构建期间合并,并映射到 Python 侧可配置的虚拟文件系统挂载点,默认为 /graalpy_vfs。例如,具有真实文件系统路径 ${project_resources_directory}/org.graalvm.python.vfs/src/foo/bar.py 的 Python 文件将在 Python 中作为 /graalpy_vfs/src/foo/bar.py 访问。

使用以下 GraalPyResources 工厂方法来创建预配置用于虚拟文件系统的 GraalPy 上下文:

  • GraalPyResources.createContext()
  • GraalPyResources.contextBuilder()
  • GraalPyResources.contextBuilder(VirtualFileSystem)
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 上下文:

  • 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.xmlbuild.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.xmlbuild.gradle 中明确指定的以及它们的所有传递性依赖项。

graalpy.lock 文件应提交到版本控制系统(例如 git)。一旦 graalpy.lock 文件存在,Maven 或 Gradle 构建期间的包安装将安装与 graalpy.lock 中捕获的完全相同的版本。

pom.xmlbuild.gradle 中显式依赖项的集合发生变化且不再与 graalpy.lock 中的内容匹配时,构建将失败,并要求用户显式重新生成 graalpy.lock 文件。

请注意,除非需要特定版本的包,否则我们建议在 pom.xmlbuild.gradle 中指定显式依赖项时不要使用版本限定符。对于某些知名的包,GraalPy 会自动安装已知与 GraalPy 兼容的版本。但是,一旦安装,版本应锁定以确保可重现的构建。

有关特定 Maven 或 Gradle 锁定包操作的信息,请参阅下面的“锁定 Python 包”小节。

GraalPy Maven 插件

Maven 插件配置

pom.xml 文件的 graalpy-maven-pluginconfiguration 块中添加插件配置。

<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>
    

锁定 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.lockgraalPyLockFile 元素本身不会触发锁定。锁定必须通过显式执行 org.graalvm.python:graalpy-maven-plugin:lock-packages 目标来完成。
    <configuration>
        <graalPyLockFile>${basedir}/graalpy.lock</graalPyLockFile>
        ...
    </configuration>
    

GraalPy Gradle 插件

Gradle 插件配置

插件必须添加到 build.gradle 文件的 plugins 部分。version 属性定义要使用的 GraalPy 版本。

plugins {
    // other plugins ...
    id 'org.graalvm.python' version '24.2.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.lockgraalPyLockFile 元素本身不会触发锁定。锁定必须通过显式执行 graalPyLockPackages 任务来完成。``` graalPy { graalPyLockFile = file("$rootDir/graalpy.lock") … }

Python 嵌入的权限

Python 脚本的访问控制和安全限制

将 GraalPy 嵌入 Java 配合 GraalVM Polyglot 沙箱 工作。

Python 的 POSIX 接口

操作系统接口暴露给 Python 脚本的方式是 GraalPy 特定的。默认情况下,所有访问都通过 Java 接口路由,但某些包依赖于 POSIX API 的细节并需要直接的本地访问。

Graal 语言(在 Truffle 框架 上实现的)通常使用 Truffle 抽象层 实现系统相关函数,该抽象层与操作系统无关,并为用户在将 GraalPy 或其他 Graal 语言嵌入 Java 应用程序时提供扩展点。例如,请参见 Truffle FileSystem 服务提供者

标准 Python 库也提供 OS 抽象,但暴露了更底层的接口。例如,os 模块直接暴露了一些 POSIX 函数。在非 POSIX 平台上,此接口在一定程度上被模拟。

GraalPy 提供了两种替代实现(每种都称为“后端”)来处理 os 等内置 Python 模块提供的系统相关功能。PosixModuleBackend 选项决定使用哪个后端:有效值为 nativejava

原生后端

此后端直接调用 POSIX API,其方式与 CPython(参考 Python 实现)大致相同。

这种方法与 CPython 的兼容性最高,并提供对底层 OS 接口的裸访问,无需中间仿真层。

默认情况下,此实现绕过 Truffle 抽象层,因此它不是沙箱化的,不支持 Truffle FileSystem 服务提供者 以及其他与系统接口相关的 Polyglot API 提供者的自定义实现。

当 GraalPy 通过 graalpy 或任何其他 Python 相关启动器启动时,默认选择原生后端。

原生后端的局限性

已知的局限性有:

  • 不支持 os.fork
  • _posixsubprocess.fork_exec 不支持 preexec_fn 参数

Java 后端

此后端使用 Truffle 抽象层,因此支持与系统接口和沙箱相关的自定义 Polyglot API 提供者。由于此抽象与 POSIX 无关,因此它不暴露所有必要的功能。某些功能被模拟,某些功能不受支持。

当 GraalPy 通过 Context API 运行,即 嵌入到 Java 应用程序中 时,Java 后端是默认选项。

Java 后端的局限性

GraalPy 可以记录有关运行时执行函数(包括 OS 接口相关函数)的已知不兼容性信息。要启用此日志记录,请使用命令行选项 --log.python.compatibility.level=FINE(或其它所需的日志级别)。

Java 后端的已知局限性有:

  • 其状态与实际 OS 状态不一致,这尤其适用于:
    • 文件描述符:Python 级别的文件描述符在原生代码中不可用。
    • 当前工作目录:在启动时初始化为当前工作目录,但随后独立维护。因此,例如,Python 中的 chdir 不会改变进程的实际当前工作目录。
    • umask:与当前工作目录具有相同的限制,但它始终初始化为 0022,无论启动时的实际系统值如何。
  • 文件访问/修改时间的解析取决于 JDK。Java 后端能保证的最佳精度是秒级。
  • os.access 和任何其他基于 faccessat POSIX 函数的功能不支持:
    • 有效 ID。
    • follow_symlinks=False,除非模式仅为 F_OK

Python 本机扩展

Python 本机扩展默认作为本机二进制文件运行,具有对底层系统的完全访问权限。请参阅 嵌入限制

运行本机扩展所需的上下文权限有:

.allowIO(IOAccess.ALL)
.allowCreateThread(true)
.allowNativeAccess(true)

Python 的工具支持

调试

GraalPy 提供了标准的 Python 调试器 pdb。有关用法,请参阅官方的 PDB 文档。内置的 breakpoint() 函数默认使用 pdb

GraalPy 还内置支持通过 Chrome 开发者工具进行图形化调试。要启用调试器,请传递 --inspect 命令行选项。您可以检查变量、设置监视表达式、交互式评估代码片段等。

  1. 使用命令行选项 --inspect 运行 Python 脚本
     graalpy --inspect my_script.py
    
  2. 您应该看到类似以下的输出:
     Debugger listening on ws://127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo
     For help, see: https://graalvm.java.net.cn/tools/chrome-debugger
     E.g. in Chrome open: devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo
    
  3. 打开您的 Chrome 浏览器并输入提供的 URL。现在您可以检查堆栈、变量、评估变量以及工具提示中选定的表达式等。例如:

    Chrome Inspector

分析

GraalPy 提供三种主要的分析功能:CPU 采样器、CPU 跟踪器和内存跟踪器。这些功能在下面进行描述。(有关详细信息,请使用 graalpy --help:tools 命令。)

CPU 采样器

使用 --cpusampler 命令行选项进行 CPU 采样。例如:

graalpy --cpusampler my_script.py

您应该看到类似以下的输出:

CPU 采样器输出(点击展开)
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 564 samples with period 10ms. Missed 235 samples.
  Self Time: Time spent on the top of the stack.
  Total Time: Time spent somewhere on the stack.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Thread[main,5,main]
 Name                                                                       ||             Total Time    ||              Self Time    || Location            
--------------------------------------------------------------------------------------------------------------------------------------------------------------
 parse_starttag                                                             ||             1090ms  19.3% ||              570ms  10.1% || <install-dir>/lib/python3.10/html/parser.py~300-347:11658-13539
 match                                                                      ||              190ms   3.4% ||              190ms   3.4% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~320-323:9862-10026
 _replace_cdata_list_attribute_values                                       ||              190ms   3.4% ||              190ms   3.4% || <venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py~295-331:11245-13031
 goahead                                                                    ||             1430ms  25.4% ||              150ms   2.7% || <install-dir>/lib/python3.10/html/parser.py~133-250:4711-9678
 check_for_whole_start_tag                                                  ||              130ms   2.3% ||              130ms   2.3% || <install-dir>/lib/python3.10/html/parser.py~351-382:13647-14758
 <module>                                                                   ||              800ms  14.2% ||              130ms   2.3% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~1-1296:0-47061
 ...
--------------------------------------------------------------------------------------------------------------------------------------------------------------

CPU 跟踪器

使用 --cputracer --cputracer.TraceStatements 命令行选项跟踪 CPU 使用情况。例如:

graalpy --cputracer --cputracer.TraceStatements my_script.py

您应该看到类似以下的输出:

CPU 跟踪器输出(点击展开)
--------------------------------------------------------------------------------------------------------------------
Tracing Histogram. Counted a total of 1135 element executions.
  Total Count: Number of times the element was executed and percentage of total executions.
  Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
  Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
--------------------------------------------------------------------------------------------------------------------
 Name                                |          Total Count |    Interpreted Count |       Compiled Count | Location
--------------------------------------------------------------------------------------------------------------------
 get_newfunc_typeid                  |           110   9.7% |           110 100.0% |             0   0.0% | capi.c~596:0
 PyTruffle_PopulateType              |           110   9.7% |           110 100.0% |             0   0.0% | capi.c~721:0
 PyTruffle_AllocMemory               |            86   7.6% |            86 100.0% |             0   0.0% | obmalloc.c~77:0
 PyTruffle_AllocateType              |            66   5.8% |            66 100.0% |             0   0.0% | capi.c~874:0
 PyMem_RawMalloc                     |            66   5.8% |            66 100.0% |             0   0.0% | obmalloc.c~170:0
 initialize_type_structure           |            50   4.4% |            50 100.0% |             0   0.0% | capi.c~181:0
 _Py_TYPE                            |            45   4.0% |            45 100.0% |             0   0.0% | object_shared.c~55:0
 PyType_GetFlags                     |            41   3.6% |            41 100.0% |             0   0.0% | typeobject_shared.c~44:0
 get_tp_name                         |            37   3.3% |            37 100.0% |             0   0.0% | capi.c~507:0
 ...    
--------------------------------------------------------------------------------------------------------------------
内存跟踪器

使用 --memtracer --memtracer.TraceStatements 命令行选项跟踪内存使用情况。例如:

graalpy --experimental-options --memtracer --memtracer.TraceStatements my_script.py

您应该看到类似以下的输出:

内存跟踪器输出(点击展开)
----------------------------------------------------------------------------
 Location Histogram with Allocation Counts. Recorded a total of 565 allocations.
   Total Count: Number of allocations during the execution of this element.
   Self Count: Number of allocations in this element alone (excluding sub calls).
----------------------------------------------------------------------------
 Name                         |      Self Count |     Total Count | Location
----------------------------------------------------------------------------
 PyTruffle_PopulateType       |      440  77.9% |      440  77.9% | capi.c~721:0
 PyType_Ready                 |       61  10.8% |       68  12.0% | typeobject.c~463:0
 _PyObject_MakeTpCall         |       20   3.5% |       24   4.2% | object.c~155:0
 PyUnicode_FromString         |       11   1.9% |       11   1.9% | capi.c~2161:0
 PyErr_NewException           |       11   1.9% |       11   1.9% | capi.c~1537:0
 _PyUnicode_AsASCIIString     |        6   1.1% |        6   1.1% | capi.c~2281:0
 PyDict_New                   |        4   0.7% |        4   0.7% | capi.c~1505:0
 PyTuple_New                  |        4   0.7% |        4   0.7% | capi.c~2097:0
 PyUnicode_FromStringAndSize  |        3   0.5% |        3   0.5% | unicodeobject.c~171:0
 ...
----------------------------------------------------------------------------

覆盖率

GraalPy 提供了 Coverage.py 工具 的自身实现,用于测量 Python 程序的代码覆盖率。使用 --coverage 命令行选项启用它,如下所示。(有关详细信息,请使用 graalpy --help:tools 命令。)

graalpy --coverage my_script.py

您应该看到类似以下的输出:

CPU 采样器输出(点击展开)
------------------------------------------------------------------------------------------------------------------------------------------------
Code coverage histogram.
  Shows what percent of each element was covered during execution
------------------------------------------------------------------------------------------------------------------------------------------------
 Path                                                                          |  Statements |    Lines |    Roots
------------------------------------------------------------------------------------------------------------------------------------------------
 <venv-dir>/lib/python3.10/site-packages/_distutils_hack/__init__.py           |       0.00% |    0.00% |    0.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/__init__.py                       |      56.10% |   56.14% |   55.26%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py               |      79.12% |   78.84% |   50.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_html5lib.py              |       2.41% |    2.46% |    2.38%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_htmlparser.py            |      69.08% |   68.67% |   83.33%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_lxml.py                  |       3.72% |    3.78% |    4.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/css.py                            |      32.73% |   31.48% |   15.38%
 <venv-dir>/lib/python3.10/site-packages/bs4/dammit.py                         |      65.46% |   65.29% |   24.14%
 <venv-dir>/lib/python3.10/site-packages/bs4/element.py                        |      44.15% |   43.13% |   31.08%
 <venv-dir>/lib/python3.10/site-packages/bs4/formatter.py                      |      73.49% |   74.36% |   66.67%
 <venv-dir>/lib/python3.10/site-packages/certifi/__init__.py                   |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/certifi/core.py                       |      33.33% |   33.33% |   25.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/__init__.py        |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/api.py             |      11.87% |   11.94% |   16.67%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/assets/__init__.py |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/cd.py              |      12.81% |   13.54% |    4.35%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/constant.py        |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/legacy.py          |      25.00% |   25.00% |   50.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/md.py              |      22.05% |   20.37% |   17.24%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/models.py          |      38.46% |   38.50% |    9.30%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/utils.py           |      26.79% |   26.89% |    3.33%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/version.py         |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/idna/__init__.py                      |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/collections/abc.py                               |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/contextlib.py                                    |      40.80% |   37.99% |   31.71%
 <install-dir>/lib/python3.10/copy.py                                          |      36.36% |   36.41% |   21.43%
 <install-dir>/lib/python3.10/copyreg.py                                       |       3.20% |    3.20% |    7.69%
 <install-dir>/lib/python3.10/csv.py                                           |      25.17% |   23.91% |   25.00%
 <install-dir>/lib/python3.10/datetime.py                                      |      30.32% |   30.01% |   14.74%
 <install-dir>/lib/python3.10/email/__init__.py                                |      42.86% |   42.86% |   20.00%
 <install-dir>/lib/python3.10/email/_encoded_words.py                          |      35.11% |   34.44% |   14.29%
 <install-dir>/lib/python3.10/email/_parseaddr.py                              |      12.64% |   12.15% |   10.71%
 <install-dir>/lib/python3.10/email/_policybase.py                             |      55.22% |   54.69% |   39.29%
 <install-dir>/lib/python3.10/email/base64mime.py                              |      35.00% |   35.00% |   20.00%
 <install-dir>/lib/python3.10/typing.py                                        |      49.86% |   48.93% |   34.60%
 <install-dir>/lib/python3.10/urllib/__init__.py                               |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/warnings.py                                      |      21.29% |   20.77% |   25.00%
 <install-dir>/lib/python3.10/weakref.py                                       |      37.93% |   36.78% |   23.68%
 <install-dir>/lib/python3.10/zipfile.py                                       |      17.86% |   17.23% |   11.03%
 <src-dir>/my_script.py                                                        |     100.00% |  100.00% |  100.00%
------------------------------------------------------------------------------------------------------------------------------------------------

跟踪

还提供了标准的 Python trace 模块。

注意:这与 CPython 的工作方式相同。程序化 API 也可用,但有一些限制:它目前不跟踪调用,只跟踪行数和被调用的函数。

例如,运行此命令:

graalpy -m trace -c -s text_styler.py Welcome to GraalPy!

您应该看到类似以下的输出:

CPU 跟踪器输出(点击展开)
_       __     __                             __     
| |     / /__  / /________  ____ ___  ___     / /_____
| | /| / / _ \/ / ___/ __ \/ __ `__ \/ _ \   / __/ __ \
| |/ |/ /  __/ / /__/ /_/ / / / / / /  __/  / /_/ /_/ /
|__/|__/\___/_/\___/\____/_/ /_/ /_/\___/   \__/\____/
                                                        
   ______                 ______        __
  / ____/________ _____ _/ / __ \__  __/ /
 / / __/ ___/ __ `/ __ `/ / /_/ / / / / /
/ /_/ / /  / /_/ / /_/ / / ____/ /_/ /_/ 
\____/_/   \__,_/\__,_/_/_/    \__, (_)  
                              /____/     
 
lines   cov%   module   (path)
    9   100%   __about__   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py)
   51   100%   __future__   (<install-dir>/lib/python3.10/__future__.py)
    1   100%   __init__   (<venv-dir>/lib/python3.10/site-packages/pyfiglet/fonts/__init__.py)
   27   100%   _adapters   (<install-dir>/lib/python3.10/importlib/_adapters.py)
   25   100%   _common   (<install-dir>/lib/python3.10/importlib/_common.py)
   44   100%   _manylinux   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py)
   20   100%   _musllinux   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py)
   66   100%   _osx_support   (<install-dir>/lib/python3.10/_osx_support.py)
   43   100%   _parseaddr   (<install-dir>/lib/python3.10/email/_parseaddr.py)
   62   100%   _policybase   (<install-dir>/lib/python3.10/email/_policybase.py)
   20   100%   _structures   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py)
  105   100%   abc   (<install-dir>/lib/python3.10/importlib/abc.py)
   18   100%   actions   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/actions.py)
   41   100%   appdirs   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py)
   59   100%   base64   (<install-dir>/lib/python3.10/base64.py)
   14   100%   base64mime   (<install-dir>/lib/python3.10/email/base64mime.py)
   11   100%   bisect   (<install-dir>/lib/python3.10/bisect.py)
  124   100%   calendar   (<install-dir>/lib/python3.10/calendar.py)
   94   100%   charset   (<install-dir>/lib/python3.10/email/charset.py)
  122   100%   common   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/common.py)
   40   100%   context   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/context.py)
    3   100%   contextlib   (<install-dir>/lib/python3.10/contextlib.py)
   91   100%   copy   (<install-dir>/lib/python3.10/copy.py)
 1497   100%   core   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/core.py)
  108   100%   dataclasses   (<install-dir>/lib/python3.10/dataclasses.py)
   31   100%   datetime   (<install-dir>/lib/python3.10/datetime.py)
    9   100%   encoders   (<install-dir>/lib/python3.10/email/encoders.py)
 2493   100%   entities   (<install-dir>/lib/python3.10/html/entities.py)
   58   100%   errors   (<install-dir>/lib/python3.10/email/errors.py)
   49   100%   exceptions   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py)
    5   100%   expat   (<install-dir>/lib/python3.10/xml/parsers/expat.py)
   41   100%   feedparser   (<install-dir>/lib/python3.10/email/feedparser.py)
   45   100%   functools   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/functools.py)
   69   100%   gettext   (<install-dir>/lib/python3.10/gettext.py)
   56   100%   header   (<install-dir>/lib/python3.10/email/header.py)
  162   100%   helpers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/helpers.py)
    1   100%   inspect   (<install-dir>/lib/python3.10/inspect.py)
   47   100%   linecache   (<install-dir>/lib/python3.10/linecache.py)
   95   100%   markers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py)
  192   100%   more   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/more.py)
  204   100%   optparse   (<install-dir>/lib/python3.10/optparse.py)
   14   100%   os   (<install-dir>/lib/python3.10/os.py)
  167   100%   parse   (<install-dir>/lib/python3.10/urllib/parse.py)
   19   100%   parser   (<install-dir>/lib/python3.10/email/parser.py)
  242   100%   pathlib   (<install-dir>/lib/python3.10/pathlib.py)
   66   100%   pkgutil   (<install-dir>/lib/python3.10/pkgutil.py)
  137   100%   platform   (<install-dir>/lib/python3.10/platform.py)
  102   100%   plistlib   (<install-dir>/lib/python3.10/plistlib.py)
   79   100%   pprint   (<install-dir>/lib/python3.10/pprint.py)
   54   100%   queue   (<install-dir>/lib/python3.10/queue.py)
   21   100%   quopri   (<install-dir>/lib/python3.10/quopri.py)
   32   100%   quoprimime   (<install-dir>/lib/python3.10/email/quoprimime.py)
  101   100%   random   (<install-dir>/lib/python3.10/random.py)
   43   100%   recipes   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/recipes.py)
   51   100%   requirements   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py)
   46   100%   resources   (<install-dir>/lib/python3.10/importlib/resources.py)
  155   100%   results   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/results.py)
   79   100%   selectors   (<install-dir>/lib/python3.10/selectors.py)
   30   100%   signal   (<install-dir>/lib/python3.10/signal.py)
   94   100%   socket   (<install-dir>/lib/python3.10/socket.py)
  143   100%   specifiers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py)
   50   100%   string   (<install-dir>/lib/python3.10/string.py)
  118   100%   subprocess   (<install-dir>/lib/python3.10/subprocess.py)
   96   100%   sysconfig   (<install-dir>/lib/python3.10/sysconfig.py)
   67   100%   tags   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py)
  119   100%   tempfile   (<install-dir>/lib/python3.10/tempfile.py)
   35   100%   testing   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/testing.py)
    7   100%   text_styler   (<src-dir>/text_styler.py)
   51   100%   textwrap   (<install-dir>/lib/python3.10/textwrap.py)
    2   100%   threading   (<install-dir>/lib/python3.10/threading.py)
   32   100%   tokenize   (<install-dir>/lib/python3.10/tokenize.py)
   43   100%   traceback   (<install-dir>/lib/python3.10/traceback.py)
  703   100%   typing   (<install-dir>/lib/python3.10/typing.py)
  238   100%   unicode   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/unicode.py)
   76   100%   util   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/util.py)
   20   100%   utils   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py)
    1   100%   version   (<venv-dir>/lib/python3.10/site-packages/pyfiglet/version.py)
   16   100%   warnings   (<install-dir>/lib/python3.10/warnings.py)
  127   100%   weakref   (<install-dir>/lib/python3.10/weakref.py)
  432   100%   zipfile   (<install-dir>/lib/python3.10/zipfile.py)

使用 PyCharm 与 GraalPy

您可以在 PyCharm 中使用 GraalPy 创建虚拟环境、安装包以及开发和运行您的 Python 应用程序。

  1. 安装 graalpy。(更多信息请参见 安装 GraalPy。)

  2. 安装 PyCharm。(更多信息请参见 安装 PyCharm。)

  3. 创建或打开一个 Python 项目。(更多信息请参见 创建 Python 项目打开、重新打开和关闭项目。)

  4. 为您的 Python 项目创建一个新的 venv 虚拟环境。(更多信息请参见 创建虚拟环境。)

  5. 按照 PyCharm 说明安装包。(更多信息请参见 安装、卸载和升级包。)

  6. 使用 PyCharm 菜单项 运行您的 Python 应用程序。或者,使用终端模拟器运行 graalpy 命令。

使用 Visual Studio Code 与 GraalPy

您可以在 Visual Studio (VS) Code 中使用 GraalPy 创建虚拟环境、安装包以及开发和运行您的 Python 应用程序。

  1. 安装 graalpy。(更多信息请参见 安装 GraalPy。)

  2. 按照此处的说明安装 VS Code 和 Python 扩展:安装 Visual Studio Code 和 Python 扩展

  3. 创建或打开一个 Python 文件。

  4. 为您的 Python 项目创建一个新的 venv 虚拟环境。(更多信息请参见 创建环境。)

  5. 按照 VS Code 说明安装包。(更多信息请参见 安装和使用包。)

  6. 使用 VS Code 菜单项运行您的 Python 应用程序。(更多信息请参见 运行 Hello World。)或者,使用 VS Code 终端模拟器运行 graalpy 命令。

  7. 您不能使用 VS Code 调试您的 Python 应用程序。相反,请打开 VS Code 终端模拟器并按照以下说明操作:调试 Python 应用程序

GraalPy 故障排除

[[TOC]]

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 资源提取到用户定义的目录中,然后配置上下文以使用该目录:
      // extract the Python resources from the jar or native image into a given directory
      GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath);
      // create a GraalPy context configured with an external Python resource directory
      Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();
      
  • 或者,如果您能够将资源发布到单独的目录中,则必须在 Maven 中设置 externalDirectory 标签或在 Gradle 中设置 externalDirectory 字段,并相应地配置 GraalPy 上下文以使用该目录。
    MavenGradle 中的 externalDirectory 字段,并且还要配置 GraalPy 上下文以使用该目录。
    // create a Context configured with an external Python resource directory
    Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();
    

    注意,如果从虚拟文件系统切换到外部目录,则以前虚拟文件系统资源根目录中的所有用户文件也必须移到该目录中。

有关 GraalPy 嵌入中 Python 资源的更多详细信息,请参阅 嵌入构建工具 文档。

有关 GraalPy 上下文和虚拟文件系统配置的更多详细信息,请参阅 GraalPyResourcesVirtualFileSystem javadoc。

GraalPy ‘java’ POSIX 后端问题

虚拟文件系统构建在 Truffle 文件系统之上,并依赖于 GraalPy Java POSIX 后端。不幸的是,某些 Python 包绕过 Python 的 I/O,直接通过其本地扩展访问文件。如果您遇到类似以下错误:

NotImplementedError: 'PyObject_AsFileDescriptor' not supported when using 'java' posix backend

那么您必须通过设置 native POSIX 后端并完全省略运行时的虚拟文件系统来覆盖默认的 java GraalPy 后端选项。

根据您在应用程序中 部署 Python 资源 的方式,您可以执行以下操作之一:

  • 如果您需要将 Python 资源打包到 Jar 或 Native Image 可执行文件中,则:
    // extract the Python resources from the jar or native image into a given directory
    GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath);
    // create a Context.Builder configured with an external python resource directory 
    Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath);
    // override the python.PosixModuleBackend option with "native"
    builder.option("python.PosixModuleBackend", "native");
    // create a context
    Context context = builder.build();
    
  • 或者,如果您能够将 Python 资源发布到单独的目录中,则必须在 Maven 中设置 externalDirectory 标签,或者在 Gradle 中设置 externalDirectory 字段,并相应地配置 GraalPy 上下文。
    MavenGradle 中的 externalDirectory 字段,并相应地配置 GraalPy 上下文。
    // create a Context.Builder configured with an external python resource directory 
    Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath);
    // override the python.PosixModuleBackend option with "native"
    builder.option("python.PosixModuleBackend", "native");
    // create a context
    Context context = builder.build();
    

    注意,如果从虚拟文件系统切换到外部目录,则以前虚拟文件系统资源根目录中的所有用户文件也必须移到该目录中。

有关 GraalPy 嵌入中 Python 资源的更多详细信息,请参阅 嵌入构建工具 文档。

有关 GraalPy 上下文和虚拟文件系统配置的更多详细信息,请参阅 GraalPyResourcesVirtualFileSystem javadoc。

Maven 和 Gradle 应用程序

ImportError 报告“未知位置”

在运行 GraalPy Java 嵌入项目时,ImportError 结束时带有 (unknown location) 的一个可能原因是嵌入的 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 依赖项。

请注意,这也适用于您的依赖项被另一个artifact(例如 micronaut-graalpy)传递性拉取的情况。

以下 artifact 无法解析:org.graalvm.python:python-language-enterprise

python-language-enterprise 已停产,请改用 org.graalvm.polyglot:python

在使用 Maven 或 Gradle GraalPy 插件在 OSX 上安装 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 发行版中与 GraalPy Maven artifact 版本匹配的启动器。

例如:

mvn package -Dgraalpy.vfs.venvLauncher={graalpy_directroy}/Contents/Home/bin/graalpy
在模块路径上找不到语言和多语言实现。

如果您看到类似以下错误:

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:pythonorg.graalvm.polyglot:python-community 添加到您的依赖项中。