69、利用 jythonc 编译 Jython:全面指南

利用 jythonc 编译 Jython:全面指南

1. 引言

Jython 凭借其对 Python 和 Java 的全面集成而脱颖而出。大多数多级语言组合在语言之间存在语义差距,阻碍了真正全面的集成。例如,CPython 在使用 C 库时需要特殊步骤,编写 C 扩展也必须遵循特定的准则。而 Jython 则允许在 Python 环境中无缝使用任意 Java 类,无需额外步骤、修改或特殊处理。不过,要实现真正的全面性,Jython 还必须让 Java 能够无缝使用 Python,而 jythonc 就满足了这一需求。

2. 什么是 jythonc?

jythonc 是一个从 Jython 模块生成 Java 代码的工具。它还可以创建 jar 文件、跟踪依赖项、冻结相关模块等,具体功能取决于所提供的选项。在 Jython shell 脚本(或批处理文件)所在的同一目录中,应该有另一个名为 jythonc(或 jythonc.bat)的脚本。这个脚本实际上是对 sys.prefix/Tools/jythonc/jythonc.py 文件的包装,该文件负责创建 Java 文件并调用 Java 编译器来创建 *.class 文件。

这意味着你必须有一个合适的 Java 编译器,如 Sun 的 javac(可从 http://www.javasoft.com/j2se/ 获得)或 IBM 的 jikes(目前可从 http://www10.software.ibm.com/developerworks/opensource/jikes/ 获得),才能与 jythonc 配合使用。Java 运行时环境(JRE)本身是不够的,因为它不包含编译器,你需要 Java 开发工具包(JDK)或 Jikes 编译器。需要注意的是,Microsoft 的 jvc 编译器目前不是编译 Jython 代码的好选择。

另外,编译后的 Jython 与解释型 Python 相比并没有性能优势。对于 Jython 新手来说,这通常是一个直接关注的问题,所以值得提前提及。目前的实现所产生的编译后的 Jython 代码,其性能与使用 Jython 本身执行模块类似。

3. 使用 jythonc 编译模块

如果你运行 jython A.py ,意味着模块 A 作为顶级(或 __main__ )脚本运行。如果你使用 jythonc 编译 A.py ,则可以直接使用 Java 来执行该应用程序。以下是一个简单的 Jython 模块 guess.py ,它将作为将 Jython 模块编译为可运行 Java 类文件的测试对象:

#file: guess.py 
import random 
print "I'm thinking of a number between 1 and 100. Guess what it is." 
num = random.randint(1, 100) 
while 1: 
    guess = int(raw_input("Enter guess: ")) 
    if guess==num: 
        print "Correct, the number was %i" % num 
        break 
    elif abs(guess - num) < 3: 
        print "Close enough. The number was %i" % num 
        break 
    elif guess > num: 
        print "Too high." 
    else: print "Too low" 

编译上述代码的 shell 命令如下:

> jythonc guess.py 

上述命令假设 jythonc 在你的路径中。如果不在,你必须给出 jythonc 的完整路径,例如:

c:\jython-2.1\jythonc guess.py 

运行 jythonc 命令后,你应该会看到类似以下的输出:

processing guess 
Required packages: 
Creating adapters: 
Creating .java files: 
guess module 
Compiling .java to .class... 
Compiling with args: ['javac', '-classpath', 
'"C:\\jython\\jython.jar;.\\jpywork;;C:\\jython\\Tools\\jythonc;C:\\ch8 
examples\\.;C:\\jython\\Lib;C:\\jython"', '.\\jpywork\\guess.java'] 
0 

根据你使用的 JDK 版本,你可能还会收到以下警告:

Note: Some input files use or override a deprecated API. 
Note: Recompile with -deprecation for details. 

从输出中可以看出,jythonc 从 guess.py 模块创建了 guess.java 文件,然后调用 javac 将 guess.java 文件编译为类文件。用于将 guess.java 编译为类的完整命令以列表形式显示(可以使用 .join(args) 来获取实际命令)。我们知道 jythonc 调用了 javac 进行编译,因为 javac 是参数列表中的第一项。需要注意的是,生成的 Java 文件可能包含特定于你使用的 JVM 版本的代码。这意味着使用 -target 选项可能不够,因为生成的代码可能包含对不支持的类的引用。要为特定的 JVM 版本编译 Jython 代码,你应该在运行 jythonc 时使用该版本的 JVM,这对于 applet 来说似乎最为必要。

如果 javac 不是你使用的 Java 编译器,你可以在注册表或命令行中指定不同的编译器。Jython 注册表键 python.jythonc.compiler -C 命令行开关都可以指定使用哪个 Java 编译器。使用 jikes 而不是 javac 的相应注册表行如下:

python.jythonc.compiler = /path/to/jikes # for *nix users 
python.jythonc.compiler = c:\path\to\jikes # for win users 

使用 -C 选项指定 jikes 编译器的命令如下:

bash> jythonc -C /path/to/jikes guess.py 
dos> jythonc -C c:\path\to\jikes guess.py 

jythonc guess.py 的结果是一个名为 jpywork 的目录,其中包含 guess.java guess.class guess$_PyInner.class 文件。 guess.java 文件是 jythonc 生成的,两个类文件是编译 guess.java 的结果。编译 Jython 代码至少应该生成两个类文件:一个是模块名称的类文件,另一个是关联的 $_PyInner 类文件。

使用 JVM 可执行文件运行 jythonc 编译的类时,要求 jython.jar 文件和两个编译后的类文件( guess.class guess$_PyInner.class )都在类路径中。如果你在编译 guess.py 生成的两个类文件所在的同一目录中,并且使用 Sun JDK 中的 Java 可执行文件,运行编译后的 guess 文件的命令如下:

bash> java -cp /path/to/jython.jar:. guess 
dos> java -cp \path\jython.jar;. guess 

你应该会被提示猜测一个数字,运行该程序的示例输出如下:

I'm thinking of a number between 1 and 100. Guess what it is. 
Enter guess: 50 
Too high. 
Enter guess: 25 
Close enough. The number was 23 

首次运行 jythonc 编译的文件时,忘记在类路径中包含适当的目录似乎是一个比较常见的错误。由于 jythonc 编译的文件通常在现有的 Java 框架中使用,编译后的类文件经常被移动到该框架的特定目录中。这一步似乎容易出错,比如没有复制所有必需的文件。要记住,jythonc 至少会创建两个文件。如果类路径中没有内部文件(在我们的例子中是 guess$_PyInner.class ),你应该会收到以下错误消息:

Error running main. Can't find: guess$_PyInner 

此外,jythonc 编译的类需要 jython.jar 文件中的类。可以使用特殊的 jythonc 选项将这些类包含在存档中,我们稍后会看到。目前,我们必须确保 jython.jar 文件在类路径中。如果类路径中不包含 jython.jar 文件,你应该会收到以下错误消息:

Exception in thread "main" java.lang.NoClassDefFoundError: 
org/python/core/PyObject 

guess.py 的编译版本仍然依赖于 sys.prefix/Lib 目录中的 random 模块。如果 sys.prefix sys.path 变量错误,你将收到以下错误:

Java Traceback: 
... 
Traceback (innermost last): 
  File "C:\WINDOWS\Desktop\ch8examples\guess.py", line 0, in main 
ImportError: no module named random 
4. 路径和编译后的 Jython

Jython 依赖于一个名为 python.home 的 Java 属性来定位和读取 Jython 注册表文件,并建立 sys.prefix ,从而确定某些 sys.path 条目。Jython 还会在 python.home sys.prefix 目录中创建一个缓存目录,默认名称为 cachedir 。关于 Java 包的发现信息会存储在这个目录中,这是一项重要的优化,但如果 python.home sys.prefix 的值不符合预期,可能会带来麻烦。 python.home 属性是在 jython 的启动脚本中设置的,但编译后的 Jython 没有这个便利。

这就引出了关于 Jython 路径的问题。一个编译为 Java 类的 Jython 模块需要特殊步骤来确保它能够找到依赖的 Jython 模块和包,并使用特定的 cachedir 。以下是你可以选择的一些选项:

4.1 在 JVM 中设置 python.home 属性

Sun 的 Java 可执行文件允许使用 -D 命令行选项设置属性。以下是一个用于暴露路径问题的 Jython 模块 paths.py

# file: paths.py 
import sys 
import java 
print "sys.path=", sys.path 
print "sys.prefix=", sys.prefix 
print "python.home=", java.lang.System.getProperty("python.home") 
# print a random number just so this depends on a 
# module in the sys.prefix/Lib directory 
import random 
print "My lucky number is", random.randint(1,100) 

使用 jythonc paths.py 编译 paths.py 。这样编译会创建 Jython 术语中所谓的可运行类文件。“可运行”意味着 Jython 代码被编译成 Java 可执行文件可以执行的形式,但它仍然依赖于 Jython 库,并遵循标准的 Jython 解释器行为。执行可运行类的行为应该与运行 Jython 脚本类似,即在启动时缓存 Java 包信息,并使用 sys.path 来定位模块。这意味着需要设置 python.home 属性或至少指定库路径。

为了继续测试 paths.py ,在命令 shell 中切换到 paths.class paths$_PyInner.class 文件所在的目录。然后使用 -D 选项设置 python.home 属性来执行 paths.class 。命令应该类似于以下内容,只是使用你自己安装的特定目录(由于行太长,命令会换行,但实际上是一个命令):

bash> java -Dpython.home="/usr/local/jython-2.1" -cp /usr/local/jython.jar:. 
paths 
dos> java -Dpython.home="c:\jython-2.1" -cp c:\jython-2.1\jython.jar;. paths 

结果应该类似于以下内容:

sys.path= ['.', 'c:\\jython 2.1\\Lib'] 
sys.prefix= c:\\jython 2.1 
python.home= c:\\jython 2.1 
My lucky number is 96 

这意味着找到了 Jython 的注册表文件, sys.prefix 值准确,并且可以找到并加载 sys.prefix/Lib 目录中的模块,如 random 模块。

如果不使用 -D 选项会怎样呢?没有它,命令将如下所示:

bash> java -cp /path/to/jython.jar:. paths 
dos> java -cp c:\path\to\jython.jar;. paths 

如果 jython.jar 文件仍然在 Jython 安装目录中,结果应该类似于以下内容(将 / 替换为你平台特定的目录分隔符):

sys.path= ['.', '/usr/local/jython-2.1/Lib'] 
sys.prefix= /usr/local/jython 2.1a1/ 
python.home= None 
My lucky number is 97 

Jython 在没有正确的 python.home 值的情况下如何知道正确的 sys.prefix 值呢?这不是 Jython 文档化的行为,但目前,如果 jython.jar 不在当前目录中, sys.prefix 值将变为找到 jython.jar 文件的目录。如果你将 jython.jar 文件复制到与 paths.class 文件相同的目录中,结果将不同:

dos> java -cp jython.jar;. paths 
sys.path= ['.', '\\Lib'] 
sys.prefix=. 
python.home= None 
Java Traceback: 
        at org.python.core.Py.ImportError(Py.java:180) 
... 
        at paths.main(paths.java:72) 
Traceback (innermost last): 
  File "C:\WINDOWS\Desktop\ch8examples\paths.py", line 0, in main 
ImportError: no module named random 

最后一种情况的结果是 python.home 等于 None 。这意味着没有找到 Jython 注册表文件,并且无法找到 Jython 的 Lib 目录中的模块(如 random 模块)。这可以通过将 random 模块也移动到同一目录来解决,因为 sys.path 会自动包含当前目录。

4.2 显式地在模块中向 sys.path 添加目录

有时,在你打算编译的模块中显式地将库路径附加到 sys.path 变量可能是最简单的方法。这可以确保特定目录中的模块是可访问的。如果你想确保 paths.py 能够找到 /usr/local/jython-2.1/Lib 目录中的 random 模块,可以在 paths.py 中添加以下行(当然是在导入 sys 之后):

sys.path.append("/usr/local/jython-2.1/Lib") # *nix 
sys.path.append("c:\jython-2.1\Lib") # dos 

作为编译类的一部分,无论 python.home 属性和 sys.prefix 变量指向何处,该目录中的模块都将始终被找到。然而,这缺乏 Java 类路径的灵活性。如果应用程序被移动到另一台机器上,路径会变得麻烦,跨平台的功能也会受到影响。

4.3 添加到 python.path python.prepath 属性

就像你可以使用 Java 的 -D 开关设置 python.home 属性一样,你也可以使用 -D 开关设置注册表的 python.path python.prepath 属性。假设你已经使用 jythonc paths.py 编译了 paths.py ,并且将 jython.jar 文件移动到了与 paths.class paths$_PyInner.class 文件相同的目录中。你可以使用类似以下的命令来运行该应用程序:

dos>java -Dpython.path="c:\\jython-2.1\\Lib" -cp jython.jar;. paths 

在这种情况下, sys.path 列表在初始化时会附加 python.path 值,因此在加载模块之前就会完成。我们的示例应用程序将能够正确地定位和加载 random 模块。在命令行中设置路径属性允许批处理文件或脚本动态确定该值,这比在代码中放置静态路径有所改进。

4.4 冻结应用程序

冻结应用程序意味着以一种在编译过程中跟踪所有依赖项的方式编译 Jython 模块,并为所有必需的模块生成类文件。一个冻结的应用程序是自包含的:它不依赖于 Jython 的 sys.path 目录,它在 Java 的类路径上定位模块,并且在启动时不缓存包信息。冻结应用程序需要使用 jythonc 的 -d --deep 选项。要冻结 paths.py ,可以使用以下命令:

dos> jythonc -d paths.py 
dos> jythonc --deep paths.py 

编译过程现在将包括 random 模块。jythonc 会跟踪依赖项,如 random 模块,并将这些模块也编译成 Java。冻结 paths 应用程序(来自 paths.py )的结果应该是四个类文件:
- paths.class
- paths$_PyInner.class
- random.class
- random$_PyInner.class

这些类文件,加上 jython.jar 中的类,就是运行 paths 应用程序所需的全部内容。

如果你在命令提示符下,并且位于编译 paths.py 生成的类文件所在的同一目录中,可以使用以下命令执行冻结后的 paths 程序:

bash> java -cp /usr/local/jython-2.1/jython.jar:. paths 
dos> java -cp c:\jython-2.1\jython.jar;. paths 

需要注意的是, jython.jar 中的类仍然是必需的,并且必须出现在类路径中。

分别编译 paths.py 然后再编译 random.py 是行不通的。jythonc 必须在编译时知道依赖项是要作为 Java 类还是 Jython 模块。这种类/模块的二分法引出了另一个重要的点。使用 jythonc 编译模块生成的类文件不能像模块一样在 Jython 中使用。jythonc 已经将它们变成了 Java 类,要将它们转换回模块是很棘手的。

4.5 编写自定义的 __import__() 钩子

编写自己的导入工具可以提供更大的控制权,但也有一些限制。使用自定义导入,你可以控制模块加载的位置。这允许你使用 zip 文件、jar 文件、类路径资源、远程 URL 资源等作为模块的源。然而,目前处理预编译模块存在困难。Python 模块(*.py 文件)和 Py 类(编译成 $py.class 文件的模块)的双重性使得在 Jython 中加载预编译模块变得麻烦。能够加载编译后的 Jython 模块( $py.class )而不必冻结所有模块依赖项是一个被称为“穷人的冻结”的理想特性,但目前尚未实现。

以下是一个简单的自定义模块加载实现 customimp.py ,它从类路径加载模块( .py 文件)。这允许访问打包在 jar 文件中的 .py 文件,这在交付应用程序时通常是需要的。在 customimp.py 中, loadmodule 是自定义导入函数, myTest.py 模块是使用 loadmodule 加载的简单配套模块。

# file: customimp.py 
import sys 
import new 
import jarray 
import java 
def loadmodule(name, bufferSize=1024): 
    filename = name + ".py" 
    cl = sys.getClassLoader() 
    if cl == None: 
        cl = java.lang.ClassLoader.getSystemClassLoader() 
    r = cl.getResourceAsStream(filename) 
    if not r: 
        raise ImportError, "No module named %s" % name 
    moduleString = "" 
    buf = jarray.zeros(bufferSize, "b") 
    while 1: 
        readsize = r.read(buf) 
        if readsize==-1: break 
        moduleString += str(java.lang.String(buf[0:readsize])) 
    sys.modules[name] == mod = new.module(name) 
    exec(moduleString) in mod.__dict__ 
    return mod 
myTest = loadmodule("myTest") 
print myTest.test() 

用于测试自定义加载的简单 myTest.py 模块如下:

# file: myTest.py 
def test(): 
    return "It worked" 

customimp.py 是一个不错的折衷方案,因为即使你冻结了应用程序,你仍然可以灵活地使用 loadmodule 加载的 .py 文件。任何必需的 .py 文件都可以从资源中更新、添加或删除,而无需重新编译任何内容。

要冻结 customimp.py 并将所需的 Jython 类全部包含在一个单独的 jar 文件中,可以使用以下 jythonc 命令:

jythonc -all --jar myapp.jar customimp.py 

现在可以使用以下命令将 myTest.py 文件添加到 myapp.jar 中:

jar -uf myapp.jar myTest.py 

myapp.jar 文件包含运行应用程序所需的一切,但你可以轻松地更新 myTest.py 文件而无需重新编译任何内容。以下命令运行 customimp 模块:

>java -cp myapp.jar customimp 
It worked 
5. jythonc 选项

jythonc 有许多选项,允许对编译后的文件、依赖项、编译器选项等进行特殊处理。jythonc 的语法要求先指定选项,然后是要编译的模块:

jythonc options modules 

模块可以是 Python 模块的完整路径,例如:

jythonc /usr/local/jython2.1/Lib/ftplib.py 

或者, sys.path 中的模块可以只列出在导入语句中使用的模块名称:

jythonc ftplib 

以下是 jythonc 的选项及其说明:
| 选项 | 描述 |
| — | — |
| --deep -d | 该选项定位并编译应用程序所需的所有依赖项,创建一个所谓的“冻结”或自包含的应用程序。冻结的应用程序可以在不访问 Jython Lib 目录的情况下运行,并且在启动时不缓存 Java 包信息。 |
| --jar jarfile -j jarfile | 该选项自动应用 --deep 选项,并将编译结果放入指定的 jar 文件中。 -jar 选项必须指定一个 jar 文件名称。这不会更新现有的 jar 文件,如果指定名称的 jar 文件已经存在,它将被覆盖。 |
| --core -c | 该选项自动应用 --deep 选项,并将核心 Java 类添加到 --jar 选项指定的 jar 文件中。该选项旨在与 --jar 选项一起使用。如果没有出现 --jar 选项, --core 选项将被忽略。 |
| --all -a | 该选项自动应用 --deep 选项,并将 org.python.core org.python.parser org.python.compiler 包中的类添加到 --jar 选项指定的 jar 文件中。生成的 jar 文件包含运行 Jython 应用程序所需的所有文件。该选项旨在与 --jar 选项一起使用。如果没有出现 --jar 选项, --all 选项将被忽略。 |
| --addpackages packages -A | 该选项将指定的 Java 包添加到 --jar 选项指定的 jar 文件中。当前实现添加的是包层次结构(目录)中的类文件,而不是 jar 文件的内容。 |
| --workingdir directory -w directory | jythonc 默认使用一个名为 jpywork 的目录来创建 Java 文件并编译为类。该选项允许你指定另一个目录来进行此操作。 |
| --skip modules -s modules | 该选项需要一个逗号分隔的模块名称列表,列表中的所有模块名称都将从编译中排除。 |
| --compiler compilerName -C complierName | 该选项允许你指定使用哪个 Java 编译器。如果所需的编译器在路径中,其名称就足够了;否则需要路径和名称。你可以使用 --compiler 选项 NONE 让 jythonc 在创建 Java 类后停止。Jython 的注册表文件还包含键 python.jythonc.compiler ,可用于设置默认编译器。 |
| --compileropts options -J | 该选项允许你指定传递给所使用的 Java 编译器的选项。例如, --compileropts -O 选项将把优化( -O )选项传递给 javac(如果使用的是该编译器)。 |
| --package packageName -p packageName | 该选项将编译后的代码放入指定的 Java 包中。访问编译后的代码需要使用包名来限定类。使用 -p test 编译模块 A 意味着未来对 A 的引用需要 test.A 。该选项与 --deep 选项一起使用最为有用。 |
| --bean jarfile -b jarfile | 该选项创建一个包含适当 bean 清单文件的 jar 文件。如果正在编译的 Jython 类遵循 bean 约定,并且你希望在 bean 框中使用创建的 jar 文件,这将很有用。 |
| --falsenames names -f names | 该选项将一个逗号分隔的名称列表设置为 false 。 |
| --help -h | 该选项将使用信息打印到屏幕上。 |

以下是 jythonc 的示例用法及其说明:
| 命令 | 结果 |
| — | — |
| jythonc test.py | 这将 test.py 编译为可运行的类文件,这些文件可能仍然依赖于 Jython 模块。 |
| jythonc -d -A utest -j A.jar test.py | 这将 test.py 编译为一个冻结的应用程序。 -d (深度)选项跟踪所有依赖项。 -A utest 选项将 utest 包中的 Java 类添加到生成的 jar 文件中。 -j A.jar 选项告诉 jythonc 将所有生成的文件放入 A.jar 文件中。 |
| jythonc -p unit -j test.jar *.py | 这将当前目录中的所有模块编译为 Java 类文件。由于 -p 选项,所有类文件将成为 Java 包 unit 的一部分,并且由于 -j 选项,它们将被放入 test.jar 文件中。 |
| jythonc -b myBean.jar namebean.py | 这将 namebean.py 模块编译,并将生成的类文件放入 myBean.jar 文件中。这还会将适当的 bean 清单添加到 myBean.jar 文件中。 |
| jythonc --core -j test.jar test.py | 这将 test.py 编译为一个冻结的应用程序。由于 --core 选项意味着 --deep 选项,所有依赖项都将被跟踪并编译。此外,Jython 的核心 Java 类也将被添加到 jar 文件中。 |

6. Java 兼容类

编译后的 Jython 类如果具有 Java 兼容性,就可以像原生 Java 类一样运行。要使 Jython 类具备 Java 兼容性,需要遵循以下三条准则:
- 子类化 Java 类或接口 :如果不需要特定 Java 类的功能,可以子类化 java.lang.Object 。不过,这仅适用于需要让编译后的 Jython 类伪装成 Java 类的情况,对于可运行或冻结的应用程序则不适用。
- 使用 @sig 字符串 :这是一种具有特定格式的文档字符串,格式为 @sig 后跟有效的 Java 方法签名。例如,对于以下 Jython 方法:

def greet(self, name): 
    return "Hello " + name 

添加 @sig 字符串后如下:

def greet(self, name): 
    "@sig public String greet(String name)" 
    return "Hello " + name 

这里的 @sig 指定了一个公共方法,返回值为 String 对象,参数 name 也是 String 对象。从 Java 角度看,这个方法就如同具有 @sig 字符串中指定签名的 Java 代码。

并非所有方法都需要 @sig 字符串。如果重写了 Java 超类中的方法(见第一条要求),则无需指定方法签名,因为 jythonc 可以从 Java 超类中获取这些信息。此外,如果某个方法从不打算从 Java 调用,也无需指定 @sig 字符串。
- 类名与包含它的模块名匹配 :例如,以下是一个名为 message.py 的 Jython 模块,其中包含一个名为 message 的类,满足此要求。

6.1 一个 Java 兼容的 Jython 类示例

message.py 中的 message 类有两个方法: __init__ showMessage ,其中 showMessage 方法有 @sig 字符串,而 __init__ 方法没有,但仍可从 Java 访问,因为 jythonc 能从 Java 超类确定其信息。

# file: message.py 
import java 

class message(java.awt.Frame): 
    def __init__(self, name): 
        self.setTitle(name) 
        self.label = java.awt.Label("", java.awt.Label.CENTER) 
        action = lambda x: java.lang.System.exit(0) 
        button = java.awt.Button('OK', actionPerformed=action) 
        p = java.awt.Panel(java.awt.GridLayout(2,1,2,2)) 
        p.add(self.label) 
        p.add(button) 
        self.add(p) 
    def showMessage(self, text): 
        "@sig public void showMessage(String text)" 
        self.label.setText(text) 
        self.pack() 
        self.show() 
if __name__ == '__main__': 
    m = message("Test Message") 
    m.showMessage("I look like Java, but I'm not") 

要在 Java 中使用 message 类,需用 jythonc 编译,命令如下(假设在 message.py 所在目录且 jythonc 在路径中):

jythonc message.py 

运行此命令的输出类似如下:

processing message 
Required packages: 
  java.lang 
  java.awt 
Creating adapters: 
  java.awt.event.ActionListener used in message 
Creating .java files: 
  message module 
    message extends java.awt.Frame 
Compiling .java to .class... 
Compiling with args: ['C:\\JDK1.3.1\\bin\\javac', '-classpath', '"C:\\jython 
2.1a1\\jython.jar;;.\\jpywork;;C:\\jython 
2.1a1\\Tools\\jythonc;C:\\WINDOWS\\Desktop\\ch8examples\\.;C:\\jython 
2.1a1\\Lib;C:\\jython 2.1a1;c:\\jython 2.1a1\\Lib\\site-python"', 
'.\\jpywork\\message.java'] 
0 

编译完成后,要在 Java 中使用 message 类,需将生成的 message.class message$_PyInner.class 以及 jython.jar 文件放入类路径。以下是使用 Sun JDK 中的 javac 编译和运行的示例:

javac -classpath .;/path/to/jython.jar MessageTest.java 
java -cp .;/path/to/jython.jar MessageTest 

运行 MessageTest 类后,会出现一个类似图 8.1 的小消息框。

需要注意的是,在 @sig 字符串中提供特定的方法签名并不会自动支持重载方法。如果 Jython 重写了 Java 方法,必须处理该名称的所有重载方法,不能仅通过添加 @sig 字符串来绕过此规则。

6.2 重载方法和 jythonc

以下示例展示了重载方法和 jythonc 的情况:

// file: Base.java 
public class Base {
    public String getSomething(String s) {
        return "string parameter. Base class."; 
    } 
    public String getSomething(int i) {
        return "int parameter. Base class."; 
    } 
    public String getSomething(float f) {
        return "float parameter. Base class."; 
    } 
} 
# file: Sub.py 
import Base 
class Sub(Base): 
    def getSomething(self, var): 
        "@sig public String getSomething(String var)" 
        return str(type(var)) + " Jython subclass.." 
// file: App.java 
import Sub; 
public class App {
    public static void main(String args[]) {
        Sub s = new Sub(); 
        System.out.println(s.getSomething("A string")); 
        System.out.println(s.getSomething(1)); 
        System.out.println(s.getSomething((float)1.1)); 
    } 
} 

编译步骤如下:
1. 将上述文件放在同一目录。
2. 切换到该目录。
3. 依次执行以下命令:

javac Base.java 
jythonc -w . Sub.py 
javac -classpath . App.java 

运行 App.java 的命令如下:

dos>java -cp .;\path\to\jython.jar App 
*nix>java -c; .:/path/to/jython.jar App 

运行结果类似如下:

org.python.core.PyString Jython subclass. 
org.python.core.PyInteger Jython subclass. 
org.python.core.PyFloat Jython subclass. 

可以看到,jythonc 自动拦截了 Base 类中名为 getSomething 的所有重载方法,并将其导向 Jython 子类中的单个同名方法。

7. 模块全局对象和 Java 兼容类

添加 Java 方法签名的严格性可能与 Jython 的动态特性相矛盾。许多 Jython 中的常见模式依赖于动态特殊方法,如 __getattr__ __setattr__ ,或者其他难以转换为编译时检查的机制。此外,使用 jythonc 创建 Java 兼容类时,模块全局对象可能不太清晰。虽然 Jython 类和带有 @sig 字符串的方法可以从 Java 调用,但模块全局对象不能。不过,Jython 类可以使用模块级代码,模块级代码甚至可以改变 Java 使用的类的细节。

以下示例展示了如何在编译的 Jython 类中使用 Jython 的动态特性,并明确模块全局对象:

# file: JavaPrototype.py 
import java.lang.Object 
class JavaPrototype(java.lang.Object): 
    def test(self): # This will become static-like 
        "@sig public void test(String arg)" 
class staticfunctor(java.lang.Object): 
    def __call__(self, arg): 
        print "printing %s from static method" % arg 
JavaPrototype.__dict__['test'] = staticfunctor() 

上述代码通过以下步骤创建了类似静态方法的效果:
1. 创建一个虚假方法和 @sig 字符串,诱使 jythonc 添加所需的 Java 方法签名。
2. 定义一个实现 __call__ 的类, __call__ 定义是实际所需的功能,因此方法签名应适合其需求。
3. 将虚假方法的标识符赋值给包含所需 __call__ 方法的类的实例。

在 Jython 中测试 JavaPrototype 模块时,可以发现多个类实例共享同一个 test 函数实例:

>>> import JavaPrototype 
>>> p1 = JavaPrototype.JavaPrototype() 
>>> p2 = JavaPrototype.JavaPrototype() 
>>> p1.test == p2.test 
1 
>>> p1.test is p2.test 
1 
>>> print id(p1.test), id(p2.test) 
5482965 5482965 

需要注意的是,编译后的 Jython 类是为在 Java 中使用而设计的,在 Jython 中应使用模块,而将 jythonc 编译的类保留用于 Java。

以下是一个简单的 Java 测试类,用于确认编译后的 JavaPrototype 类在 Java 中正常工作:

// file: Test.java 
import JavaPrototype; 
public class Test {
    public static void main(String[] args)) {
        JavaPrototype i1 = new JavaPrototype(); 
        JavaPrototype i2 = new JavaPrototype(); 
        i1.test("THIS"); 
        i2.test("THAT"); 
    } 
} 

编译和运行步骤如下:
1. 将 Test.java 放在与 JavaPrototype.class JavaPrototype$_PyInner.class 相同的目录。
2. 执行编译命令:

javac -classpath . test.java 
  1. 运行命令:
# dos 
java -cp .;\path\to\jython.jar test 
# *nix 
java -cp .:/path/to/jython.jar test 

运行结果如下:

printing THIS from static method 
printing THAT from static method 

总结

本文全面介绍了使用 jythonc 编译 Jython 代码的相关内容,涵盖了 jythonc 的基本概念、使用方法、路径处理、选项设置以及创建 Java 兼容类等方面。通过具体的代码示例和详细的操作步骤,展示了如何利用 jythonc 将 Jython 模块编译为可在 Java 环境中运行的类文件,以及如何处理编译过程中可能遇到的问题。同时,还探讨了 Jython 代码与 Java 代码的交互和兼容问题,为开发者在实际项目中使用 Jython 和 Java 提供了有价值的参考。

操作流程总结

以下是使用 jythonc 编译 Jython 代码的一般操作流程:

graph LR
    A[准备工作] --> B[选择合适的 Java 编译器]
    A --> C[确保 jythonc 在路径中]
    B --> D[编写 Jython 模块]
    D --> E[使用 jythonc 编译模块]
    E --> F{是否需要特殊处理}
    F -- 是 --> G[根据需求设置 jythonc 选项]
    F -- 否 --> H[检查编译结果]
    G --> H
    H --> I{是否为 Java 兼容类}
    I -- 是 --> J[遵循 Java 兼容类准则]
    I -- 否 --> K[直接使用编译后的类文件]
    J --> K
    K --> L[将所需文件放入类路径]
    L --> M[使用 Java 运行编译后的应用程序]

注意事项

  • 编译后的 Jython 与解释型 Python 相比没有性能优势。
  • 运行 jythonc 编译的类文件时,要确保 jython.jar 和编译后的类文件都在类路径中。
  • 处理路径问题时,可根据具体情况选择合适的方法,如设置 python.home 属性、向 sys.path 添加目录等。
  • 使用 jythonc 选项时,要注意各选项的功能和使用场景。
  • 编写 Java 兼容类时,要严格遵循相关准则。
<think>我们正在讨论Jython集成。根据用户的问题,用户想了解Jython集成的定义及其在开发中的用途。 根据提供的引用,我们可以总结如下: 引用[2]中提到:Jython可以用于跨语言开发与集成。例如,一个用Jython编写的数据分析脚本,其核心算法可以通过jythonc编译Java类,然后集成Java开发的企业级系统中。 引用[3]提到:Jython的自动bean属性和事件属性在处理GUI时非常有用,特别是在AWT和Swing类中。 引用[4]提到:使用jythonc编译的类与JSP配合使用,需要创建满足特定条件的Jython类(如类名与模块名相同,继承自Java类等)。 引用[5]提到:Jython在服务器端Web编程中的应用,包括Servlet、JSP等,并且可以与新兴技术(如微服务、容器化)结合。 因此,我们可以将Jython集成定义为:将Jython(一种在Java虚拟机上运行的Python实现)与Java代码、Java库以及Java平台的其他部分(如Servlet容器)无缝结合的过程。这种集成允许开发者在同一个项目中同时使用Python和Java,发挥两种语言的优势。 在开发中的用途包括: 1. 跨语言开发:在Java应用中嵌入Python脚本,或者在Python脚本中调用Java类库。 2. 企业级系统集成:将用Jython编写的模块(如数据处理脚本)编译Java类,然后集成Java企业应用中。 3. GUI开发:利用Jython的简洁语法和Java的Swing/AWT库来开发图形用户界面。 4. Web开发:使用Jython编写Servlet和JSP,运行在Java Servlet容器(如Tomcat)中。 5. 脚本扩展:为Java应用提供脚本支持,允许用户编写Python脚本来扩展应用功能。 下面我们按照用户要求组织回答,并生成相关问题。</think>### Jython集成的定义与开发用途 #### 1. **Jython集成的定义** Jython集成指在Java虚拟机(JVM)环境中无缝融合Python与Java代码的技术方案。其核心是通过Jython(Python的Java实现)实现: - **双向互操作**:Java可直接调用Python脚本,Python也能访问Java类库(如Swing/AWT、Servlet API等)[^3][^5]。 - **编译级融合**:通过`jythonc`工具将Python代码编译Java字节码,生成与原生Java兼容的`.class`文件[^2][^4]。 - **语法适配**:支持Python语法调用Java Bean属性和事件,简化GUI开发[^3]。 #### 2. **在开发中的核心用途** 1. **跨语言开发与系统集成** - 将Python脚本(如数据分析模块)编译Java类,直接嵌入Java企业系统(如Spring框架)[^2]。 - 示例:用Jython的Pandas库清洗数据后,通过`jythonc`编译集成Java金融系统[^2]。 2. **服务器端Web开发** - 编写Jython Servlet/JSP,运行于Tomcat等Servlet容器,复用Java生态(如数据库连接池、安全框架)[^5]。 - 优势:Python简化业务逻辑,Java保障高并发性能[^5]。 3. **GUI应用快速开发** - 利用Python简洁语法操作Java Swing/AWT组件,自动绑定Bean属性和事件[^3]。 - 示例:用`JButton(text="Submit", actionPerformed=handler)`替代Java冗长监听器[^3]。 4. **遗留系统扩展** - 为Java旧系统添加Python脚本扩展点,支持动态热更新业务规则。 #### 3. **技术优势** | 场景 | 优势 | |---------------------|----------------------------------------------------------------------| | **企业级系统** | 复用Java安全、事务管理机制,兼容微服务/容器化架构[^5] | | **科学计算** | 调用NumPy/Pandas处理数据,结果直传Java可视化库 | | **自动化测试** | 用Python脚本驱动Java应用测试(如Selenium+TestNG) | --- ### 相关问题 1. **Jython与CPython在集成Java时的性能差异如何?未来有哪些优化方向?**[^1] 2. **使用`jythonc`编译Jython代码时,需遵循哪些规范才能确保与Java无缝对接?**[^4] 3. **在Jython Servlet开发中,如何处理数据库连接泄漏和SQL注入风险?**[^5] 4. **Jython的Bean属性简化语法在哪些场景下可能不适用?**[^3] [^1]: JVM优化可提升Jython性能,未来或支持更多Web框架 [^2]: 跨语言开发中,jythonc编译实现Python到Java集成 [^3]: Bean属性语法简化GUI事件绑定 [^4]: 编译Jython类需匹配模块名、继承Java类、标注@sig [^5]: Jython Servlet需注意资源管理、安全过滤和异常处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值