利用 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
- 运行命令:
# 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 兼容类时,要严格遵循相关准则。
超级会员免费看
40

被折叠的 条评论
为什么被折叠?



