面向 JVM 的现代 Python

对于 Python 2 版本(现已停用),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

基本对象使用 #

使用传统的 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 列表
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 异常,请使用 --python.EmulateJython 选项。

注意:捕获 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 语句:当声明的方法名称匹配时,声明的方法会覆盖(实现)超类(接口)方法。要调用超类方法,请使用特殊属性 self.__super__。创建的对象的行为不像 Python 对象,而是像外部 Java 对象一样。可以使用其 this 属性访问其 Python 级别的成员。例如

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()}')

将 Python 嵌入 Java #

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

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

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

与我们联系