深入探索Jython与Java的融合:嵌入与扩展的艺术
在软件开发的多元世界中,将不同编程语言的优势融合在一起,往往能创造出更强大、更灵活的应用程序。Jython与Java的结合就是这样一个极具潜力的组合,它允许Java应用程序借助Jython的高级动态特性,同时让Jython应用程序利用Java的高效字节码。本文将详细介绍如何在Java中嵌入和扩展Jython,为开发者提供实用的技术指导。
1. Jython与Java融合的优势
在许多编程语言中,嵌入和扩展其他语言的功能已经是常见的做法。然而,Jython与Java的组合在这方面表现得尤为出色。Python的C实现以其在语言融合方面的实用性而受到高度赞扬,而Jython不仅继承了这一优点,还通过最小化语言之间的差异,实现了Java和Jython对象的无缝传递。无论是Java对象传入Jython解释器,还是Jython对象传递给Java,都能轻松实现,而且无需为了适应另一种语言而对对象进行定制。这种无缝集成以及Jython本身的优势,使得Jython和Java成为多语言应用程序的理想选择。
2. 在Java中嵌入Jython
在Java中嵌入Jython具有多种用途,尤其在需要交互式命令解释器、生成多样化输出(如报告生成)、采用动态配置以及应用程序的特定元素需要频繁更改时非常有用。而且,随着越来越多的开发者挖掘嵌入式Jython的用途,其应用场景还会不断增加。即使不考虑设计动机,仅为了利用Jython的优势,如提高代码可读性、实现快速开发和缩短学习曲线等,也值得在Java中嵌入Jython。不过,嵌入Jython也有一些缺点,比如会增加解释器的内存开销,并需要在应用程序中引入额外的类。
2.1 嵌入所需的类
org.python.util
包中的三个类为在Java中嵌入Jython提供了必要的手段,它们分别是
PythonInterpreter
、
InteractiveInterpreter
和
InteractiveConsole
,并且它们呈现出一种层次结构,后一个类是前一个类的子类。
2.2 PythonInterpreter的使用
在Java应用程序中嵌入Jython,最常用的方法是使用
org.python.util.PythonInterpreter
类的实例。这个类包含在Jython安装的
jython.jar
文件中。在Java代码中嵌入这个类只需要添加两行语句:
import org.python.util.PythonInterpreter;
PythonInterpreter interp = new PythonInterpreter();
这里的
interp
可以看作是Java中的一个容器,Jython解释器就在这个容器中运行。Java代码对容器内的内容一无所知,容器内的对象也对外部世界毫无感知。创建这个容器非常简单,但在实例化解释器之前的步骤以及实例化后的使用方法需要特别关注。
2.3 初始化Jython环境
Jython环境依赖于许多属性,如
python.home
、
python.path
和
python.security.respectJavaAccessibility
等,这些属性通常在启动时从注册表文件加载,或者通过命令行选项设置。在Java中嵌入Jython时,可以在创建解释器对象之前显式设置这些属性,这是通过
PythonInterpreter
类的静态
initialize
方法实现的。这种对属性、路径和设置的显式控制,相比于使用
jythonc
编译的类,具有更大的优势,因为在嵌入过程中开发者可以拥有更多的控制权。
initialize
方法的签名如下:
public static void initialize(Properties preProperties,
Properties postProperties,
String[] argv)
参数可以理解为 “旧属性”、“新属性” 和 “命令行参数”。“旧属性” 通常设置为
System.getProperties()
,它包含了通过Java的
-D
命令行开关设置的属性;“新属性” 通常是一个新的
java.util.Properties
实例,并已经添加了一些条目;最后一个参数是一个字符串数组,它将成为Jython的
sys.argv
。
为了确保嵌入式解释器能够找到其主目录,可以在
initialize
方法中设置
python.home
属性,示例如下:
Properties props = new Properties();
props.put("python.home", "/usr/local/jython-2.1"); // *nix
//props.put("python.home", "c:\\jython2.1"); // windows
PythonInterpreter.initialize(System.getProperties(),
props, new String[0]);
初始化
PythonInterpreter
时还会处理Jython的注册表文件(如果找到)。注册表文件中的设置会创建第三组属性,这三组属性的加载顺序决定了在多个地方定义同一属性时的优先级。旧属性首先加载,接着是注册表属性,最后是新属性。这意味着注册表属性会覆盖旧属性,而新属性会覆盖注册表属性和系统属性。
以下是一个嵌入
PythonInterpreter
的Java类示例:
// file: Embedding.java
package org.python.demo;
import java.util.Properties;
import org.python.util.PythonInterpreter;
import java.util.Enumeration;
public class Embedding {
protected PythonInterpreter interp;
public static void main(String[] args) {
Embedding embed = new Embedding();
embed.initInterpreter(args);
embed.test();
}
protected void initInterpreter(String[] argv) {
// Get preProperties postProperties, and System properties
Properties postProps = new Properties();
Properties sysProps = System.getProperties();
// set default python.home property
if (sysProps.getProperty("python.home")==null)
sysProps.put("python.home", "c:\\jython 2.1a1");
// put System properties (those set with -D) in postProps
Enumeration e = sysProps.propertyNames();
while ( e.hasMoreElements() ) {
String name = (String)e.nextElement();
if (name.startsWith("python."))
postProps.put(name, System.getProperty(name));
}
// Here's the initialization step
PythonInterpreter.initialize(sysProps, postProps, argv);
//instantiate- note that it is AFTER initialize
interp = new PythonInterpreter();
}
public void test() {
// Print system state values to confirm proper initialization
interp.exec("import sys");
interp.exec("print"); // Add empty line for clarity
interp.exec("print 'sys.prefix=', sys.prefix");
interp.exec("print 'sys.argv=', sys.argv");
interp.exec("print 'sys.path=', sys.path");
interp.exec("print 'sys.cachedir=', sys.cachedir");
interp.exec("print"); // Another blank for clarity
}
}
这个类不仅展示了属性的设置和解释器的初始化,还可以作为本章许多示例的基础类。
test()
方法用于测试解释器命令,它通过解释器的
exec
方法执行一些Jython语句,以确认属性是否设置正确。
编译和执行这个类的命令如下:
javac -classpath /path/to/jython.jar org/python/demo/Embedding.java
java -classpath /path/to/jython.jar:. org.python.demo.Embedding
运行这个类的示例输出如下:
sys.prefix= /usr/local/jython-2.1
sys.argv= []
sys.path= ['.', '/usr/local/jython-2.1/Lib']
sys.cachedir= /usr/local/jython-2.1/cachedir
如果在命令行使用
-D
开关添加属性,可以确认属性设置是否正确。例如在Windows上的示例:
java -Dpython.cachedir="newcache" -Dpython.path="d:\devel\jython" -classpath
"c:\jython-2.1\jython.jar";. org.python.demo.Embedding
输出结果可能如下:
*sys-package-mgr*: processing new jar, 'C:\jython-2.1\jython.jar'
*sys-package-mgr*: processing new jar, 'C:\jdk1.3.1\jre\lib\rt.jar'
*sys-package-mgr*: processing new jar, 'C:\jdk1.3.1\jre\lib\i18n.jar'
*sys-package-mgr*: processing new jar, 'C:\jdk1.3.1\jre\lib\sunrsasign.jar'
sys.prefix= c:\jython-2.1
sys.argv= []
sys.path= ['.', 'c:\\jython-2.1\\Lib', 'd:\\devel\\jython']
sys.cachedir= c:\jython-2.1\newcache
需要注意的是,如果在指定位置没有缓存目录,会看到缓存相关的消息。在嵌入Jython时,搜索Java包并缓存结果的操作会在初始化时进行,而不是在
PythonInterpreter
实例化时。如果不向
initialize
方法提供系统属性,上述示例中的缓存操作将不会发生,因为解释器无法获取查找JAR文件所需的信息。
2.4 实例化解释器
PythonInterpreter
类有三个构造函数:
public PythonInterpreter()
public PythonInterpreter(PyObject dict)
public PythonInterpreter(PyObject dict, PySystemState systemState)
第一个构造函数是无参数的,使用起来比较直观。第二个构造函数接受一个
PyObject
作为参数,这个对象将成为解释器的命名空间,通常是
org.python.core.PyStringMap
对象。通过提供解释器的命名空间,可以在实例化之前设置解释器命名空间中的对象,或者在脚本调用之间存储命名空间。示例代码如下:
// the required imports
import org.python.core.*;
import org.python.util.PythonInterpreter;
// Assume the interpreter is already intitialized
PyStringMap dict = new PyStringMap();
dict.__setitem__("Name", new PyString("Strategy test"));
dict.__setitem__("Context", Py.java2py(new SomeContextClassOrBean()));
PythonInterpreter interp = new PythonInterpreter(dict);
需要注意的是,在Java中使用Jython对象时,需要显式调用Jython的特殊方法,如上述示例中的
__setitem__
方法。
第三个构造函数允许在实例化解释器时设置命名空间和
PySystemState
对象。
PySystemState
对象是Jython的
sys
模块在Java中的表示。使用
PySystemState
对象设置解释器使用的
sys.path
的示例代码如下:
PyStringMap dict = new PyStringMap();
dict.__setitem__("A", new PyInteger(1));
PySystemState sys = new PySystemState();
sys.path.append(new PyString("c:\\jython-2.1\\Lib"));
sys.path.append(new PyString("c:\\windows\\desktop"));
sys.path.append(new PyString("d:\\cvs"));
// instantiate with namespace and system state objects
interp = new PythonInterpreter(dict, sys);
如果嵌入多个解释器,它们将共享系统状态。这意味着当修改
PySystemState
对象时,所有嵌入式解释器的系统状态都会改变,但每个解释器实例的本地命名空间是唯一的。
2.5 设置输出和错误流
PythonInterpreter
实例有
setOut
和
setErr
方法,用于设置输出流和错误流。这些方法接受的参数可以是
java.io.OutputStream
、
java.io.Writer
或任何类似文件的
org.python.core.PyObject
。以下是一个使用
setErr
方法将错误消息重定向到文件的示例:
// file: ErrorRedir.java
package org.python.demo;
import org.python.demo.Embedding;
import java.io.*;
public class ErrorRedir extends Embedding {
public static void main(String[] args) {
ErrorRedir erd = new ErrorRedir();
erd.initInterpreter(args);
erd.test();
}
public void test() {
// Redirect errors to a file
try {
interp.setErr(new FileWriter(new File("errors")));
} catch (IOException e) {
e.printStackTrace();
}
interp.exec("assert 0, 'This should end up in a file.'");
}
}
编译和执行这个类的命令如下:
javac -classpath c:\path\to\jython.jar;.org\python\demo\ErrorRedir.java
java -cp c:\path\to\jython.jar;. org.python.demo.ErrorRedir
执行后,控制台只会显示以下消息:
Exception in thread "main"
其余的跟踪信息将出现在当前工作目录下的
errors
文件中。将错误流和输出流更改为网络流或其他资源也是常见的场景。
2.6 PySystemState的使用
在Jython中,
sys
模块包含系统状态信息。在嵌入Jython时,可以在解释器实例中使用
sys
对象,也可以在解释器外部使用
sys
模块的Java表示
PySystemState
。在Java中获取
PySystemState
对象的代码如下:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
获取
PySystemState
对象后,可以从Java代码中更改解释器内部的状态。以下是一个使用
PySystemState
对象向
sys.path
变量追加值的示例:
// file: SysState.java
package org.python.demo;
import org.python.demo.Embedding;
import org.python.core.*;
public class SysState extends Embedding {
public static void main(String[] args) {
SysState s = new SysState();
s.initInterpreter(args);
s.test();
}
public void test() {
System.out.println("sys.path before changes to to sys:");
interp.exec("import sys\n" +
"print sys.path\n" +
"print");
// Get the system state and append to its path
PySystemState sys = Py.getSystemState();
sys.path.append(new PyString("c:\\windows\\desktop"));
System.out.println("sys.path after changes to sys:");
interp.exec("print sys.path");
}
}
编译和执行这个类的命令如下:
javac -classpath c:\path\to\jython.jar;. org.python.demo.SysState.java
dos>\jdk1.3.1\bin\java -classpath "c:\jython-2.1\jy
thon.jar";. org.python.demo.SysState
输出结果如下:
The sys.path before changes to PySystemState:
['.', 'c:\\jython-2.1\\Lib', 'd:\\python20\\lib']
The sys.path after changes to PySystemState:
['.', 'c:\\jython-2.1\\Lib', 'd:\\python20\\lib', 'c:\\windows\\desktop']
可以看到,通过
PySystemState
对象添加的
sys.path
条目会在第二次打印
sys.path
时显示出来。如果嵌入了多个解释器,这种系统状态的更改将影响每个解释器。
Jython
的
PySystemState
类(即
sys
模块)还包含三个与类加载相关的方法:
add_package
、
add_classdir
和
add_extdir
。当Java应用程序有自己的类加载器,且与Jython的类加载器不同时,Jython可能无法正确识别或找到某些Java包,这时就需要使用这三个方法。
-
add_package方法:将指定的Java包添加到Jython的包管理器中。例如,如果公司A的应用程序有自己的类加载器,并且在其中嵌入了Jython解释器,嵌入式解释器可能无法正确识别com.A.python这样的Java包。公司A可以使用add_package方法来确保正确识别和加载该包,示例代码如下:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_package("com.A.python");
需要注意的是,
add_package
方法不会导入或加载包,应用程序的类加载器会完成这项工作,该方法只是将包添加到Jython可以导入类的Java包列表中。
-
add_classdir方法:将指定目录中的包添加到Jython搜索Java包的位置列表中。例如,如果有一个从/usr/java/devel目录开始的包和类层次结构,可以使用以下代码将其添加到Jython的包管理器中:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_classdir("/usr/java/devel/");
-
add_extdir方法:将目录中存档文件的内容添加到Jython搜索Java包的位置列表中。例如,如果将应用程序所需的JAR和ZIP文件存储在/usr/java/lib目录中,可以使用以下代码将这些存档文件的内容添加到Jython的包管理器中:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_extdir("/usr/java/lib/");
org.python.util.PyServlet
类是初始化解释器并使用这三个
add_*
方法的一个很好的例子。以下是
PyServlet
类中
init()
方法的简化版本:
public void init() {
Properties props = new Properties();
if (props.getProperty("python.home") == null &&
System.getProperty("python.home") == null)
{
props.put("python.home", rootPath + "WEB-INF" +
File.separator + "lib");
}
PythonInterpreter.initialize(System.getProperties(),
props, new String[0]);
PySystemState sys = Py.getSystemState();
sys.add_package("javax.servlet");
sys.add_package("javax.servlet.http");
sys.add_package("javax.servlet.jsp");
sys.add_package("javax.servlet.jsp.tagext");
sys.add_classdir(rootPath + "WEB-INF" +
File.separator + "classes");
sys.add_extdir(rootPath + "WEB-INF" +
File.separator + "lib");
}
需要记住的是,
add_package
、
add_classdir
和
add_extdir
方法不会加载任何内容,应用程序的类加载器会完成实际的加载工作,这些方法只是让Jython可以导入更多的Java包。这些方法不仅可以在嵌入Jython时使用,在Jython中也可以调用,例如在交互式解释器中使用
sys.add_extdir
方法:
>>> import sys
>>> sys.add_extdir("c:\\web\\tomcat\\lib")
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\ant.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\jaxp.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\servlet.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\parser.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\webserver.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\jasper.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\zxJDBC.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\tools.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\ecs.jar'
*sys-package-mgr*: processing new jar, 'C:\web\tomcat\lib\jython.jar'
>>> from javax.servlet import http
>>> dir()
['__doc__', '__name__', 'http', 'sys']
可以看到,
add_extdir
方法使
http
包变得可用,但它只是让包可用,而不是加载类。如果尝试从
ext_dirs
中导入类,可能会遇到
ImportError
异常:
>>> from javax.servlet.http import HttpServlet
Traceback (innermost last):
File "<console>", line 1, in ?
ImportError: cannot import name HttpServlet
3. 使用解释器
在解释器中运行代码需要使用解释器的
exec
、
execfile
或
eval
方法。
3.1 exec方法
exec
方法允许执行一段Python代码字符串或预编译的代码对象。执行Python代码字符串时,每次调用
exec
都必须使用完整的、语法正确的语句。
exec
方法不会返回任何对象,执行Jython代码字符串的所有结果都保留在解释器内部。
前面的示例已经展示了
exec
方法的使用,但只使用了简单的语句。复合语句的处理需要特殊的格式,例如需要转义双引号、在需要的地方添加换行符并使用正确的缩进。以下是一个在Jython中定义简单函数的示例,以及如何将其改写为在嵌入式解释器中工作的代码:
# in Jython
def movingAverage(datalist, sampleLen):
"""movingAverage(list, sampleLen) -> PyList"""
add = lambda x, y: x + y
return [reduce(add, datalist[x:x+10])/sampleLen
for x in range(len(datalist) - sampleLen)]
import random
L = [random.randint(1,100) for x in range(100)]
print movingAverage(L, 10)
// in Java
interp.exec("def movingAverage(datalist, sampleLen):\n" +
" '''movingAverage(list, sampleLength) -> PyList'''\n" +
" add = lambda x, y: x + y\n" +
" return [reduce(add, datalist[x:x+10])/sampleLen " +
" for x in range((len(datalist)-sampleLen)]\n" +
"import random\n" +
"L = [random.randint(1,100) for x in range(100)]\n" +
"print movingAverage(L, 10)");
在Java版本中,通过字符串拼接创建了一个与Jython版本等效的字符串。需要注意的是,Jython代码中的引号使用单引号以避免与Java引号冲突,如果字符串中需要双引号,要确保进行转义,并且在
exec
字符串中需要的地方添加换行符以符合Jython的语法。
3.2 execfile方法
PythonInterpreter
的
execfile
方法允许执行文件或
InputStream
。该方法有三个版本:
public void execfile(String s)
public void execfile(java.io.InputStream s)
public void execfile(java.io.InputStream s, String name)
以下是一个实现所有三个
execfile
方法的类示例:
// file: ExecFileTest.java
package org.python.demo;
import org.python.demo.Embedding;
import java.io.*;
import org.python.core.*;
public class ExecFileTest extends Embedding {
public static void main(String[] args) {
ExecFileTest eft = new ExecFileTest();
eft.initInterpreter(args);
eft.test();
}
public void test() {
PySystemState sys = Py.getSystemState();
if (sys.argv.__len__() == 0) {
System.out.println("Missing filename.\n " +
"Usage: ExecFileTest filename");
return;
}
String home = System.getProperty("user.home");
String filename = home + File.separator +
sys.argv.__getitem__(0);
// Using a file name with execfile
interp.execfile(filename);
// Using an InputStream with execfile
try {
FileInputStream s = new FileInputStream(filename);
interp.execfile(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// Using an InputStream and a name with execfile
try {
FileInputStream s = new FileInputStream(filename);
interp.execfile(s, sys.argv.__getitem__(0).toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
编译这个类的命令如下:
javac -classpath c:\path\to\jython.jar;.
org\python\demo\ExecFileTest.java
在用户主目录下创建一个名为
test.py
的Python文件,内容为
print 'It worked.'
,然后使用以下命令执行该文件三次(分别对应三个
execfile
方法):
java -cp c:\path\to\jython.jar;. org.python.demo.ExecFileTest test.py
输出结果如下:
It worked.
It worked.
It worked.
3.3 eval方法
eval
方法与
exec
方法有三个不同之处:
-
eval
方法总是返回它计算的表达式的结果,作为
org.python.core.PyObject
对象,而
exec
和
execfile
方法的返回类型总是
void
。
- 解释器的
eval
方法目前只接受代码字符串,而
exec
方法可以接受字符串或编译后的代码对象。
- 解释器的
eval
方法只计算表达式,而
exec
方法可以执行任意的Jython代码。
解释器的
eval
方法是Jython内置
eval
方法的包装器。解释器的
eval
方法接受一个代码字符串,然后调用内置
eval
方法,并进行两个小的修改:一是将解释器的本地命名空间作为第二个参数传递给内置
eval
方法;二是将代码字符串从
java.lang.String
转换为
org.python.core.PyString
对象。以下是使用解释器的
eval
方法和内置
eval
方法进行
eval
操作的示例:
// The interpreter's eval shortcut
interp.eval("1 or 0") // The interpreter's eval()
// The built-in version
__builtin__.eval(new PyString("1 or 0"), interp.getLocals())
可以看到,Jython的内置函数可以通过
org.python.core.__builtin__
类访问,大多数Jython的内置函数都可以通过这种方式在Java中直接使用。由于
eval
方法总是返回一个
PyObject
,可以对其返回的对象使用内置方法进行实验。
内置
eval
函数和解释器的
eval
方法计算的表达式有与
lambda
表达式相同的限制,即不能包含赋值或语句,代码字符串只能使用函数、方法、数据对象、字面量以及不进行名称绑定操作的运算符。
以下是一个创建交互式循环以评估表达式的示例:
// file: EvalTest.java
package org.python.demo;
import org.python.demo.Embedding;
import java.io.*;
import org.python.core.*;
public class EvalTest extends Embedding {
private static BufferedReader terminal;
public static void main(String[] args) {
EvalTest et = new EvalTest();
et.initInterpreter(args);
et.test();
}
public void test() {
System.out.println("Enter strings to evaluate at the prompt");
interact("eval> ");
}
public void interact(String prompt) {
terminal = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter \"exit\" to quit.");
String codeString = "";
while (true) {
System.out.print(prompt);
try {
codeString = terminal.readLine();
if (codeString.compareTo("exit")==0) System.exit(0);
processInput(codeString);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void processInput(String input) {
PyObject o = interp.eval(input);
System.out.println("Results is of type " +
__builtin__.type(o));
System.out.println(o);
}
}
编译和执行这个类的命令如下:
javac -classpath c:\path\to\jython.jar;. org\python\demo\EvalTest.java
java -cp c:\path\to\jython.jar;. org.python.demo.EvalTest
运行这个类的示例输出如下:
Enter strings to evaluate at the prompt
Enter "exit" to quit.
eval> 0 or 10
Results is of type <jclass org.python.core.PyInteger at 6851381>
10
eval> 1 and "A string"
Results is of type <jclass org.python.core.PyString at 7739053>
A string
eval> [x for x in range(2,37) if 37%x == 0] === []
Results is of type <jclass org.python.core.PyInteger at 6851381>
1
eval> a = 10 # Assignments are not allowed
Exception in thread "main" Traceback ((innermost last):
(no code object) at line 0
File "<string>", line 1
a = 10 # Assignments are not allowed
^
SyntaxError: invalid syntax
4. 编译代码对象以供后续使用
在Jython解释器中使用代码字符串调用
exec
和
eval
方法时,解释器会先编译代码,然后执行。编译步骤会花费时间,因此,如果应用程序有大量的Jython代码或需要多次使用相同的代码,可以在应用程序的非时间关键部分编译这些代码,以避免重复编译的开销。
使用Jython的内置
compile
函数在Java中创建编译后的代码对象需要三个步骤:
1. 导入所需的类,包括
org.python.core
包中的
__builtin__
和
PyCode
类。
2. 创建一个
java.lang.String
对象。
3. 将字符串编译成代码对象。
以下是创建一个用于
exec
方法的
PyCode
对象的示例:
// Import required classes (PyCode and __builtin__)
import org.python.core.*;
// Create a java.lang.String
String s = "print 'Hello World'";
// compile with "<>" for the file-name, and "exec" for type of object
PyCode code = __builtin__.compile(s, "<>", "exec");
// exec with interpreter. Note: no return object
interp.exec(code);
内置
compile
函数需要三个参数:代码字符串、文件名(用于错误消息)和模式。模式可以是
exec
、
eval
或
single
(如果只是单条语句)。由于上述示例中的
compile
语句创建了一个具有
exec
模式的代码对象,可以将该对象传递给嵌入式解释器的
exec
方法。嵌入式解释器的
exec
方法可以处理代码对象和字符串,而解释器的
eval
方法目前只处理字符串。如果要使用内置
eval
方法执行编译后的代码对象,可以调用
__builtin__.eval
方法,并将解释器的命名空间作为第二个参数传递。示例代码如下:
import org.python.core.*;
String s = "1 and 0 or 10";
PyCode code = __builtin__.compile(s, "<>", "eval");
// Fetch the interpreter's local namespace
PyCode locals = interp.getLocals();
// use __builtin__.eval with interp's locals
PyObject o = __builtin__.eval(code, locals);
上述代码通常可以简化为:
import org.python.core.*;
PyCode code = __builtin__.compile("1 and 0 or 10", "<>", "eval");
PyObject o = __builtin__.eval(code, interp.getLocals());
5. 处理解释器中的异常
如果解释器中出现问题,解释器会抛出
org.python.core.PyException
异常。例如,在Jython中使用
open
函数打开文件失败时,Java代码不会像Jython那样得到
IOError
异常,而是得到
PyException
异常。以下是Jython中的
try/except
语句以及如何将异常处理移到嵌入式解释器周围的Java代码中的示例:
# in Jython
try:
f = open("SomeNonExistingFile")
except IOError:
e, i, tb = sys.exc_info()
print e, "\n", tb.dumpStack()
// in Java
try {
interp.exec("f = open('SomeNonExistingFile')");
} catch (PyException e) {
e.printStackTrace();
}
PyException
堆栈跟踪会包含实际Jython异常的信息,但在
catch
子句中无法区分Jython异常的类型。即使在解释器中使用会抛出Java异常的Java对象,Jython的包装也会将异常转换为
PyException
异常。因此,在Java的
catch
子句中捕获特定的Jython异常是不可能的。可以采取以下几种方法来处理异常:
- 在Jython解释器对象中使用
try/except
语句。例如,在命令循环中需要捕获
IOException
时,可以在解释器中使用如下代码:
interp.exec("try:\n" +
" _file = open(" + filename + ")\n" +
"except:\n" +
" print 'File not found. Try again.');
- 在周围的Java代码中处理错误,避免Jython异常。例如,在命令循环中,有人输入文件名时,先测试文件是否可读,然后再在解释器中打开文件:
File file = new File(filename);
if (file.canRead()!=true) {
System.out.println("File not found. Try again.");
break;
}
// File was found and is readable- safe to open.
interp.exec("file = open(" + filename + ")");
-
在Java的
catch块中使用Py.matchException方法来辨别实际的异常类型。Py.matchException函数接受一个PyException作为第一个参数,一个Jython异常对象(如Py.IOError)作为第二个参数,并返回一个布尔值,表示PyException是否是特定的Jython异常类型。示例代码如下:
try {
interp.exec("f = open('SomeNonExistingFile')");
} catch (PyException e) {
if (Py.matchException(e, Py.AttributeError)) {
//handle Jython's AttributeError here
} else if (Py.matchException(e, Py.IOError)) {
//handle Jython's IOError here
}
}
6. set和get方法
PythonInterpreter
的
set
和
get
方法分别用于设置和获取解释器本地命名空间中的对象。
6.1 set方法
PythonInterpreter
类有两个
set
方法:
public void set(String name, Object value)
public void set(String name, PyObject value)
这两个方法的第一个参数都是
java.lang.String
对象,表示要绑定的名称。第二个参数不同,如果是Java对象,解释器会将其转换为适当的Jython类型;如果是
PyObject
对象,解释器会直接将其放入命名空间字典中,不进行修改。以下是使用
set
方法在嵌入式解释器中设置一些简单对象的示例:
interp.set("S", "String literals becomes a PyStrings in the interp");
interp.set("V", java.util.Vector()); //V becomes a PyInstance type
对于Java的基本类型,如
int
和
char
,不能直接使用,需要使用它们的
java.lang
包装类。例如:
interp.set("I", new Integer(1));
interp.set("C", new Character('c'));
set
方法的逻辑之一是将Java对象转换为适当的
PyObject
对象,Jython有自己的类型转换规则,例如,字符串对象在解释器中会变成
PyString
对象,
java.lang.Integer
会变成
PyInteger
,
java.lang.Long
会变成
PyLong
等。
6.2 get方法
PythonInterpreter
的
get
方法有两个签名:
public PyObject get(String name)
public Object get(String name, Class javaclass)
name
参数指定解释器命名空间映射对象(
locals
)中的键,
get
方法的返回值是与指定键关联的值。第一个
get
方法只需要名称参数,并返回一个
PyObject
对象;第二个
get
方法有一个额外的参数,用于指定返回对象的类。
解释器的
set
方法中Java对象的自动类型转换在
get
方法中不是可逆的。如果要使用
get
方法获取Java对象而不是
PyObject
对象,必须指定要返回的Java类,并将结果强制转换为所需的对象。以下是使用
get
方法获取
java.lang.String
对象的示例:
interp.set("myStringObject", "A string");
String s = (String)interp.get("myStringObject", String.class);
需要注意的是,返回值是一个对象,因此如果将结果赋值给一个更具体的类型,必须进行强制转换,并且第二个参数必须是一个类,而不是描述类的字符串。
以下是一个演示
set
和
get
方法的示例类:
// file: TypeTest.java
package org.python.demo;
import org.python.demo.Embedding;
import org.python.core.*;
import java.util.Vector;
public class TypeTest extends Embedding {
private boolean simple = true;
public TypeTest( ) { ; }
public static void main(String[] args) {
TypeTest tt = new TypeTest();
tt.initInterpreter(args);
tt.test();
}
public void test() {
String codeString;
Object[] jTypes = {"A string", new Short("1"), new Integer(3),
new Long(10), new Float(3.14), new Double(299792.458),
new Boolean(true), new int[] {{1,2,3,4,5}, new Vector(),
new PyInteger(1), new Character('c')};
interp.exec("print 'In Java'.ljust(20), " +
"'In Jython'.ljust(20), " +
"'Back in Java'");
interp.exec("print '------'.ljust(20), " +
"'---------'.ljust(20), " +
"'------------'");
// get first command-line argument- argv[0]
PyObject argv = Py.getSystemState().argv;
if (argv.__len__() > 0) {
String option = argv.__getitem__(0).toString();
if (option.compareTo("symmetrical")==0) simple = false;
}
for ( int i=0; i < jTypes.length; i++ ) {
showConversion(jTypes[i]);
}
}
public void showConversion(Object o) {
interp.set("testObject", o);
interp.set("o", o.getClass().toString());
String newClass = null;
if (simple) {
newClass = interp.get("testObject").getClass().toString();
} else {
newClass = interp.get("testObject",
o.getClass()).getClass().toString();
}
interp.set("n", newClass); // n for newJavaClass
interp.exec("pyClass = str(testObject.__class__) \n" +
"print o[o.rfind('.') + 1:].ljust(20), " +
"pyClass[pyClass.rfind('.') + 1:].ljust(20), " +
"n[n.rfind('.') + 1:]");
}
}
编译这个类的命令如下:
javac -classpath c:\path\to\jython.jar;. org\python\demo\TypeTest.java
不使用命令行参数运行这个类的输出如下:
In Java In Jython Back in Java
------- --------- -----------
String PyString PyString
Short PyInteger PyInteger
Integer PyInteger PyInteger
Long PyLong PyLong
Float PyFloat PyFloat
Double PyFloat PyFloat
Boolean PyInteger PyInteger
class [I PyArray PyArray
Vector Vector PyJavaInstance
PyInteger PyInteger PyInteger
Character PyString PyString
可以看到,解释器的自动转换是单向的。如果在命令中添加
symmetrical
选项,将尝试将每种类型转换回其原始的Java类:
C:\WINDOWS\Desktop\ch9examples>java -classpath "c:\jython-2.1\jython.jar";.
org.python.demo.TypeTest symmetrical
输出结果如下:
In Java In Jython Back in Java
------- ---------- -----------
String PyString String
Short PyInteger Short
Integer PyInteger Integer
Long PyLong Long
Float PyFloat Float
Double PyFloat Double
Boolean PyInteger Boolean
class [I PyArray class [I
Vector Vector Vector
PyInteger PyInteger PyInteger
Character PyString Character
除了使用
get
方法进行类型转换外,还有另一种更常用的方法,即
__tojava__
方法。
7. __tojava__方法
Jython类(
PyObjects
)的实例有一个特殊的方法
__tojava__
,该方法接受一个Java类作为参数,并返回实例强制转换为请求的Java类的结果。如果无法进行强制转换,该方法返回
Py.NoConversion
对象。以下是将
PyString
转换为
java.lang.String
对象的示例:
interp.set("A = 'A test string'");
PyObject po = interp.get("A");
String MyString = (String)po.__tojava__(String.class);
上述示例假设
__tojava__
方法成功执行。如果转换失败,
__tojava__
方法将返回
Py.NoConversion
对象,这会导致
(String)
强制转换抛出
ClassCastException
异常。为了处理这种情况,可以在强制转换之前测试是否返回了
Py.NoConversion
对象。以下是一个测试
__tojava__
方法转换失败的示例:
// file: Convert.java
package org.python.demo;
import org.python.demo.Embedding;
import org.python.core.*;
public class Convert extends Embedding {
public Convert() { ; }
public static void main(String[] args) {
Convert c = new Convert();
c.initInterpreter(args);
c.test();
}
public void test() {
// Set an identifier in the interpreter
interp.exec("test = 'A'");
// Get object out of interpreter as PyObject
PyObject retrievedObject = interp.get("test");
// Convert PyObject to instance of desired Class
Object myObject = retrievedObject.__tojava__(Character.class);
// See if conversion failed- meaning Py.NoConversion returned
if (myObject == Py.NoConversion) {
System.out.println("Unable to convert.");
} else {
Character myChar = (Character)myObject;
System.out.println("The Character is: " + myChar);
}
}
}
使用
__tojava__
方法将用户定义的Jython类转换为Java对象,可以使Jython编写的类在需要特定Java类的地方使用。例如,一个应用程序使用Java类
A
,并且需要大量不同版本的该类,可以在Jython中编写这些类作为
A
的子类,并在需要时将它们转换为
A
类的对象,这可能允许快速开发这些所需的类。以下是一个使用
__tojava__
方法进行报告生成的示例:
// file: Report.java
package org.python.demo.reports;
import org.python.demo.Embedding;
import org.python.demo.reports.ReportSpec;
import java.io.*;
import org.python.core.*;
public class Report extends Embedding {
public static void main(String[] args) {
Report rpt = new Report();
rpt.initInterpreter(args);
try {
rpt.generateReport();
} catch (Exception e) {
e.printStackTrace();
}
}
public void generateReport() throws FileNotFoundException, PyException {
String fileName;
ReportSpec rs = null;
// Check #1- user supplied command-line arg
PySystemState sys = Py.getSystemState();
if (sys.argv.__len__() == 0) {
System.out.println("Missing filename.\n " +
"Usage: 'Report' filename");
return;
} else {
fileName = sys.argv.__getitem__(0).toString();
}
// Check #2- Command-line arg is in fact a *.py file
if (new File(fileName).isFile() != true)
throw new FileNotFoundException(fileName);
if (fileName.endsWith(".py") != true)
throw new PyException(
new PyString(fileName + " is not a *.py file"));
try {
rs = getReportSpecInstance(fileName);
} catch (InstantiationException e) {
e.printStackTrace();
}
rs.fillTitle();
rs.fillHeadings();
while (rs.fillRow()) {}
}
protected ReportSpec getReportSpecInstance(String fileName)
throws InstantiationException
{
String className;
// Exec the file
interp.execfile(fileName);
// Get the name of the file without path and extension.
// This should be the name of the class within the file
int start = fileName.lastIndexOf(File.separator);
if (start < 0)
start = 0;
else
start++;
className = fileName.substring(start, fileName.length() - 3);
PyObject reportSpecClass = interp.get(className);
if (reportSpecClass == null)
throw new InstantiationException(
"No ReportSpec Class named " + className +
"exists in " + fileName);
PyObject m_args = (PyObject) new PyInteger(70);
PyObject reportSpecInstance = reportSpecClass.__call__(m_args);
ReportSpec rs =
(ReportSpec)reportSpecInstance.__tojava__(ReportSpec.class);
if (rs == Py.NoConversion)
throw new InstantiationException(
"Unable to create a ReportSpec instance from " +
className);
return rs;
}
}
// file: ReportSpec.java
package org.python.demo.reports;
public abstract class ReportSpec extends Object {
public abstract String fillTitle();
public abstract void fillHeadings();
public abstract boolean fillRow();
}
# file: ReportTest.py
from org.python.demo.reports import ReportSpec
class ReportTest(ReportSpec):
def __init__(self, reportWidth):
# "@sig public ReportTest(int reportWidth)"
# plug in data (a database in a real implementation)
self.width = reportWidth
self.data = [ [1,2,3],
[4,5,6],
[7,8,9] ]
self.pad = reportWidth/(len(self.data[0]) - 1)
def fillTitle(self):
print "Test of Report Generator".center(self.width)
def fillHeadings(self):
"""Prints column headings."""
# This would be database metadata in a real implementation
for x in ["A", "B", "C"]:
print x.ljust(self.pad - 1),
print
def fillRow(self):
if not self.data:
return 0
row = self.data.pop()
for x in row:
print str(x).ljust(self.pad - 1),
print
return 1
编译
Report
类和
ReportSpec
类的命令如下:
javac -classpath \path\to\jython.jar;.
org\python\demo\reports\Report.java
javac -classpath \path\to\jython.jar;.
org\python\demo\reports\ReportSpec.java
使用
ReportTest.py
作为命令行参数运行
Report
类的输出如下:
Test of Report Generator
A B C
7 8 9
4 5 6
1 2 3
8. getLocals和setLocals方法
getLocals
和
setLocals
方法分别用于获取和设置解释器的整个本地命名空间映射对象。其方法签名如下:
public PyObject getLocals()
public void setLocals(PyObject d)
通常,使用的是
org.python.core.PyStringMap
对象。使用
getLocals
和
setLocals
方法的一个重要原因是为多个脚本维护隔离的命名空间。如果模块A和模块B在解释器中顺序执行,并且都定义了相同的模块全局变量,那么模块B会重新定义该变量,这可能会导致模块A再次执行时出现问题。通过获取和恢复
locals
对象,可以消除这种意外副作用的风险。
以下是一个使用
getLocals
和
setLocals
方法的示例:
// file: LocalsTest.java
package org.python.demo;
import org.python.demo.Embedding;
import org.python.core.*;
public class LocalsTest extends Embedding {
public LocalsTest() { ; }
public static void main(String[] args) {
LocalsTest L = new LocalsTest();
L.initInterpreter(args);
L.test();
}
public void test() {
PyStringMap locals = new PyStringMap();
locals.__setitem__("Test1", new PyString("A test string"));
interp.setLocals(locals);
interp.exec("print Test1");
interp.exec("Test2 = 'Another teststring'");
PyObject dict = interp.getLocals();
System.out.println(dict);
}
}
编译这个类的命令如下:
javac -classpath c:\path\to\jython.jar;.
org\python\demo\LocalsTest.java
运行这个类的输出如下:
dos>java -cp -c:\jython\jython.jar";. org.python.demo.LocalsTest
A test string
{'Test2': 'Another teststring', 'Test1': 'A test string'}
9. imp和顶级脚本
许多Jython模块利用顶级脚本环境来有条件地运行代码的主部分,常见的语句是
if __name__ == '__main__':
。在嵌入Jython时,如果需要脚本执行与
__name__ == '__main__'
条件相关的代码,必须在解释器中显式添加
__main__
。
以下是一个设置
__name__ == '__main__'
的示例:
// file: TopLevelScript.java
package org.python.demo;
import org.python.demo.Embedding;
import java.io.*;
import org.python.core.*;
public class TopLevelScript extends Embedding {
public static void main(String[] args) {
TopLevelScript tls = new TopLevelScript();
tls.initInterpreter(args);
tls.test();
}
public void test() {
PySystemState sys = Py.getSystemState();
if (sys.argv.__len__() == 0) {
System.out.println("Missing filename.\n " +
"Usage: TopLevelScript filename");
return;
}
String filename = sys.argv.__getitem__(0).toString();
// Set __name__
PyModule mod = imp.addModule("__main__");
interp.setLocals(mod.__dict__);
// Using a file name with execfile
interp.execfile(filename);
}
}
编译这个类的命令如下
javac -classpath \path\to\jython.jar
org\python\demo\TopLevelScript.java
可以使用一个简单的Jython模块来测试
__name__
的设置:
# file: mainname.py
if __name__ == '__main__':
print "It worked."
使用以下命令运行该测试:
dos> java -classpath \path\to\jython.jar;. ort.python.demo.TopLevelScript
mainname.py
输出结果如下:
It worked.
也可以通过创建一个
PyStringMap
并将
__name__
键设置为
__main__
来达到相同的效果:
// Set __name__
PyStringMap dict = new PyStringMap();
dict.__setitem__("__name__", new PyString("__main__"));
interp.setLocals(dict);
上述示例使用
imp.addModule
方法,不仅是因为Jython本身采用这种方式,还因为借此引入了
imp
类。
imp
类实现了Jython的导入功能,
addModule
方法加载一个模块并返回
PyModule
对象,同时将模块名添加到已导入模块列表
sys.modules
中。
10. 嵌入交互式解释器
InteractiveInterpreter
是
PythonInterpreter
的子类,它提供了
runcode
、
runsource
和
interrupt
等方法,以实现更高级的交互功能。该类有两个重要的特性:异常捕获和语句完整性管理。在
runcode
或
runsource
方法中发生异常时,异常会被捕获,不会导致程序崩溃,从而允许交互继续进行。同时,解释器会打印异常信息,让用户了解发生了什么。此外,
runsource
方法返回一个布尔值,用于指示源字符串的完整性,这有助于确定打印哪个提示符以及何时重置语句缓冲区。
InteractiveInterpreter
的方法签名和用途如下表所示:
| 方法签名 | 摘要 |
| — | — |
|
public InteractiveInterpreter()
public InteractiveInterpreter (PyObject locals)
| 这些是
InteractiveInterpreter
类的构造函数。第二个构造函数接受一个
PyStringMap
对象,与
PythonInterpreter
的单参数构造函数类似。 |
|
public Boolean runsource(String source)
public Boolean runsource(String source, String filename)
public Boolean runsource(String source, String filename, String symbol)
|
runsource
方法尝试编译并执行一段代码字符串,同时显示异常信息,但不会传播异常。返回值如下:成功 ->
false
;发生异常 ->
false
;语句不完整 ->
true
。当语句不完整时返回
true
便于轻松控制收集用户输入的循环。第二和第三个参数与内置
compile
方法相同。 |
|
public void runcode(PyObject code)
|
runcode
方法执行一个代码对象,并显示执行过程中发生的任何异常。注意,异常仅显示,不会传播。 |
|
public void showexception(PyException exc)
| 将异常信息写入
sys.stderr
,主要用于内部使用。 |
|
public void write(String data)
| 将字符串写入
sys.stderr
,主要用于内部使用。 |
|
public void interrupt(ThreadState ts) throws InterruptedException
|
interrupt
方法以线程友好的方式暂停代码,插入异常。 |
以下是一个利用
runsource
返回值的循环示例,该循环会一直运行,直到用户输入一个语法完整的语句:
while (interp.runsource(codeString)) {
System.out.print(ps2);
codeString += "\n" + terminal.readLine();
}
以下是一个嵌入
InteractiveInterpreter
的示例:
// file: InteractiveEmbedding.java
package org.python.demo;
import org.python.demo.Embedding;
import org.python.util.InteractiveInterpreter;
import java.util.Properties;
import java.io.*;
public class InteractiveEmbedding extends Embedding {
protected InteractiveInterpreter interp;
public static void main(String[] args) {
InteractiveEmbedding ie = new InteractiveEmbedding();
ie.initInterpreter(args);
ie.test();
ie.interact();
}
public void initInterpreter(String[] argv) {
// set Properties
if (System.getProperty("python.home") == null)
System.setProperty("python.home", "c:\\jython-2.1");
// no postProps, all properties but python.home put in registry file
InteractiveInterpreter.initialize(System.getProperties(), null, argv);
//instantiate- note that it is AFTER initialize
interp = new InteractiveInterpreter();
}
public void test() {
interp.runsource("print \"this is a syntax error\"");
interp.runsource("print 'This is not'");
}
public void interact() {
String ps1 = ">>>";
String ps2 = "...";
BufferedReader terminal = new BufferedReader(
new InputStreamReader(System.in));
interp.write("Enter \"exit\" to quit.");
String codeString = "";
interp.write("\n");
while (true) {
interp.write(ps1);
try {
codeString = terminal.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (codeString.compareTo("exit")==0) System.exit(0);
while (interp.runsource(codeString)) {
interp.write(ps2);
try {
codeString += "\n" + terminal.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
该示例首先初始化解释器,然后运行一个简单的测试方法,最后开始一个交互循环。测试方法故意包含一个语法错误的字符串,以展示
runsource
方法如何报告错误而不终止解释器。交互循环会打印提示符,读取用户输入,并将输入传递给解释器的
runsource
方法。如果语句不完整,
runsource
方法返回
true
,这使得可以使用一个简洁的内部循环来收集复合语句的输入,直到语句完整。
编译该示例的命令如下:
javac -classpath \path\to\jython.jar;.
org\python\demo\InteractiveEmbedding.java
示例运行的输出如下:
dos>java -cp \path\to\jython;. org.python.demo.InteractiveEmbedding
Traceback (innermost last):
(no code object) at line 0
File "<input>", line 2
SyntaxError: Lexical error at line 2, column 0. Encountered: <EOF> after :
""
This is not
Enter "exit" to quit.
>>>print "Hello World!"
Hello World!
>>>try:
... assert 0, "Assertion error for testing compound statements"
...except AssertionError:
... import sys
... e = sys.exc_info()
... print "%s\n%s\n%s" % e
...
exceptions.AssertionError
Assertion error for testing compound statements
<traceback object at 2147462>
>>>exit
11. 嵌入交互式控制台
InteractiveConsole
在嵌入的基础上又增加了一层抽象,专门用于实现Jython中常见的控制台交互,例如前面提到的示例。创建控制台交互需要使用
InteractiveConsole
的三个方法:
interact
、
raw_input
和
push
。这些方法的用途总结如下表:
| 方法 | 摘要 |
| — | — |
|
public InteractiveConsole()
public InteractiveConsole (PyObject locals)
public InteractiveConsole (PyObject locals, String filename)
|
InteractiveConsole
的三个构造函数允许可选地设置解释器的本地命名空间,并设置用于错误消息的文件名。 |
|
public void interact()
public void interact(String banner)
|
interact
方法模拟Jython解释器。可选的
banner
参数是在第一次交互之前打印的消息。 |
|
public boolean push(String line)
|
push
方法将一行不以
\n
结尾的代码推送到解释器中。该方法返回
true
或
false
,与
InteractiveInterpreter
的
runsource
方法类似,
true
表示需要更多输入。 |
|
public String raw_input(PyObject prompt)
|
InteractiveConsole
的
raw_input
方法与内置
raw_input
方法相同。 |
以下是一个嵌入交互式控制台的示例:
// file: Console.java
package org.python.demo;
import org.python.util.InteractiveConsole;
import java.util.Properties;
import java.io.*;
public class Console {
protected InteractiveConsole interp;
public Console() {
// set Properties
if (System.getProperty("python.home") == null)
System.setProperty("python.home", "c:\\jython-2.1");
// no postProps, registry values used
InteractiveConsole.initialize(System.getProperties(),
null, new String[0] );
interp = new InteractiveConsole();
}
public static void main(String[] args) {
Console con = new Console();
con.startConsole();
}
public void startConsole() {
interp.interact("Welcome to your first embedded console");
}
}
该示例初始化
InteractiveConsole
并调用其
interact
方法。初始化过程与之前相同,但使用
interact
方法大大简化了交互循环。执行该示例将产生Jython的交互式控制台。需要注意的是,
InteractiveConsole
的
push
和
raw_input
方法也可以使用,但在这个示例中并不需要。这些方法主要用于交互,不适合对性能要求较高的场景。如果应用程序不需要与解释器进行交互,建议使用
PythonInterpreter
对象及其
exec
和
eval
方法。
12. 扩展Jython
扩展Jython意味着用Java编写Jython模块,这里的Jython模块指的是那些行为类似于Jython模块的Java类。与普通的Java类不同,当一个类需要支持Jython特有的功能,如有序和关键字参数时,就需要将其设计为真正的Jython模块。
以下是一个用Java编写的Jython模块示例:
// file mymod.java
package org.python.demo.modules;
import org.python.core.*;
public class mymod implements ClassDictInit {
public static void classDictInit(PyObject dict) {
dict.__setitem__("__doc__",
new PyString("Test class to confirm " +
"builtin module"));
dict.__delitem__("classDictInit");
}
public static PyString __doc__fibTest =
new PyString("fibTest(iteration) "+
"-> integer");
public static int fibTest(PyObject[] args, String[] kw) {
ArgParser ap = new ArgParser("fibTest", args, kw, "iteration");
int iteration = ap.getInt(0);
if (iteration < 1)
throw new PyException(Py.ValueError,
new PyString("Only integers >=1 allowed"));
if (iteration == 1 || iteration == 2)
return iteration;
return fibTest(new PyObject[] { new PyInteger(iteration-1) },
new String[0]) +
fibTest(new PyObject[] { new PyInteger(iteration-2) },
new String[0]);
}
}
在Jython的交互式shell中运行该模块的示例输出如下:
>>> from org.python.demo.modules import mymod
>>> import time
>>>
>>> def test(method, iteration):
... t1 = time.time()
results = apply(method, (iteration,))
... print "The 25th fibonacci iteration is: ", results
... print "Time elapsed: ", time.time() - t1
...
>>> test(mymod.fibTest, 25)
The 25th fibonacci iteration is: 121393
Time elapsed: 0.8799999952316284
与之对比,在Jython中实现类似的斐波那契函数:
>>> def fib(iteration):
... if iteration < 1: raise ValueError, "Iteration must be >=1"
... if iteration < 3: return iteration
... return fib(iteration - 1) + fib(iteration -2)
...
>>> test(fib, 25)
The 25th fibonacci iteration is: 121393
Time elapsed: 1.590000033378601
可以看出,用Java编写的Jython模块在性能上更具优势。
12.1 ClassDictInit接口
如果一个Java类实现了
ClassDictInit
接口,就可以控制该类在Jython中可见的属性名称及其实现。实现该接口的类必须有一个
classDictInit
方法,如下所示:
public static void classDictInit(PyObject dict)
Jython在类初始化时会调用
classDictInit
方法。在上述
mymod
类的示例中,
classDictInit
方法用于设置
__doc__
字符串,并将
classDictInit
方法本身从Jython可见的名称中移除。实际上,如果直接定义
__doc__
字符串,
classDictInit
方法可以省略。但当模块比较复杂,由多个类实现,或者类包含多个需要对Jython隐藏的属性时,
classDictInit
方法就会发挥作用。
此外,还有一种控制Java方法在Jython中可见性的方法,即使用
org.python.core.PyIgnoreMethodTag
异常。虽然该异常不会被实际抛出,但任何声明抛出该异常的Java方法都会自动从Jython的视图中移除。
12.2 __doc__字符串
可以通过包含一个名为
__doc__
的静态
PyString
成员来定义模块的文档字符串,例如:
public static __doc__ = new PyString("Some documentation");
类中的静态方法在Jython中会成为模块函数。在上述示例中,静态
fibTest
方法在Jython模块中表现为
fibTest
函数。为该函数添加文档字符串,可以添加一个名为
__doc__fibTest
的静态
PyString
。在Jython中可以通过
mymod.fibTest.__doc__
来检索该文档字符串。
12.3 异常处理
在Java中抛出Jython异常需要使用
PyException
类,并传入两个参数:实际的Jython异常和异常消息。Jython的内置异常位于
org.python.core.Py
类中,例如,
ValueError
实际上是
Py.ValueError
。在Jython中抛出异常的语句
raise ValueError, "Invalid Value"
在Java中的形式如下:
throw new PyException(Py.ValueError, "Invalid Value");
在上述
mymod
类的示例中,使用
PyException
在解释器中抛出了一个Jython
ValueError
异常。
12.4 参数处理
Jython函数具有丰富的参数方案,包括有序参数、关键字参数、默认值和通配符。在Java中可以模拟这种行为。例如,一个Java方法可以通过以下方式处理位置和关键字参数:
public PyObject MyFunction(PyObject[] args, String[] kw);
args
数组包含所有参数值,
kw
数组只包含指定的关键字。
org.python.core.ArgParser
类可以帮助解析这些参数。
ArgParser
类有四个构造函数:
public ArgParser(String funcname, PyObject[] args,
String[] kws, String p0)
public ArgParser(String funcname, PyObject[] args,
String[] kws, String p0, String p1)
public ArgParser(String funcname, PyObject[] args,
String[] kws, String p0, String p1, String p2)
public ArgParser(String funcname, PyObject[] args,
String[] kws, String[] paramnames)
每个构造函数的第一个参数是函数名,第二个和第三个参数分别是
args
数组和
kw
数组,其余参数是方法期望的参数列表。可以通过
ArgParser
实例的
get*
方法来获取参数值。
以下是一些Jython方法及其对应的Java实现示例:
| Jython方法 | Java实现 |
| — | — |
|
def test(A, B, C=2)
|
java public static PyObject test(PyObject[] args, String[] kws) { ArgParser ap = new ArgParser("test", args, kws, "A", "B", "C"); }
|
|
def addContact(name, addr, ph=None)
|
java public static PyObject addContact(PyObject[] args, String[] kws) { ArgParser ap = new ArgParser("addContact", args, kws, new String[] {"name", "addr", "ph"}); }
|
13. 在Java中导入Jython模块
在Java中导入Jython模块通常使用
__builtin__.__import__
函数,该函数有四个签名:
public static PyObject __import__(String name)
public static PyObject __import__(String name, PyObject globals)
public static PyObject __import__(String name, PyObject globals,
PyObject locals)
public static PyObject __import__(String name, PyObject globals,
PyObject locals, PyObject fromlist)
例如,在Java中导入
random
模块的代码如下:
PyObject module = __builtin__.__import__("random");
14. 处理PyObjects
在Java中调用Jython类和编写Jython模块需要广泛使用Jython的特殊方法。Jython的动态操作意味着查找、获取、设置和调用属性都通过一些特殊方法实现,这些方法提供了定制和扩展每个步骤的机会。除了前面提到的获取、设置和调用方法外,还有用于查找的方法,如
__findattr__
和
__finditem__
:
public PyObject __finditem__(PyObject key)
public PyObject __finditem__(int index)
public PyObject __finditem__(String key)
public PyObject __findattr__(String name)
public PyObject __findattr__(PyObject name)
需要注意的是,接受
String
对象作为参数的
__finditem__
和
__findattr__
方法要求字符串对象是被实习(interned)的。字符串字面量会自动被实习,否则需要显式调用
intern()
方法。
例如,如果要导入
random
模块并使用
randint
方法,不能直接调用,而应该使用
__findattr__
方法结合
__call__
方法:
PyObject random = __builtin__.__import__("random");
random.__findattr__("randint").__call__(new PyInteger(10),
new PyInteger(20));
为了方便在Java中调用
PyObject
的方法,提供了
invoke
方法,其不同的签名如下:
public PyObject invoke(String name)
public PyObject invoke(String name, PyObject arg1)
public PyObject invoke(String name, PyObject arg1, PyObject arg2)
public PyObject invoke(String name, PyObject[] args)
public PyObject invoke(String name, PyObject[] args, String[] keywords)
invoke
方法的第一个参数是要调用的
PyObject
方法的名称,该名称必须是实习字符串。以下是一个使用
invoke
方法获取
PyDictionary
对象键的示例:
PyDictionary dct = new PyDictionary();
dct.__setitem__(new PyString("G"), new PyInteger(1));
dct.__setitem__(new PyString("D"), new PyInteger(2));
dct.__setitem__(new PyString("A"), new PyInteger(3));
dct.__setitem__(new PyString("E"), new PyInteger(4));
dct.__setitem__(new PyString("B"), new PyInteger(5));
PyObject keys = dct.invoke("keys");
遍历
keys
对象时,由于
PyObject
的
__len__
方法可能返回错误的值,因此在Java中安全遍历Jython序列的唯一方法是逐个测试索引值,直到遇到不存在的索引:
PyObject key;
for (int i = 0; (key = keys.__finditem__(i)) != null; i++) {
System.out.println("K: " + key + ", V: " + dict.__getitem__(key));
}
15. 用Java编写Jython类
当一个Java类模拟Jython模块时,模块中的函数通常实现为静态类成员。要在Java中模拟Jython类,可以使用静态内部类,或者使用
ClassDictInit
接口以获得更大的灵活性。为了用Java模拟Jython类,最好继承
org.python.core
包中最合适的
Py*
类,并实现所需类型类的所有特殊方法。所有用Java编写的类都可以继承
PyObject
并实现
__findattr__
、
__setattr__
和
__delattr__
方法。对于映射对象,需要继承
PyDictionary
或
PyStringMap
并实现
__finditem__
、
__setitem__
、
__delitem__
以及相关的映射方法,Jython的其他数据类型也是如此。
16. 将Java类作为内置Jython模块添加
在Java中编写Jython模块时,可以将该模块指定为内置模块。通过
python.modules.builtin
注册表键,可以将模块添加到用Java编写的内置模块列表中。该属性是一个逗号分隔的列表,条目可以有三种形式,如下表所示:
| 条目语法 | 描述 |
| — | — |
|
name
| 仅指定Java类的名称,假设该类位于
org.python.modules
包中。将该条目添加到
python.modules.builtin
列表后,可以在Jython中使用
import name
导入该模块。例如,如果要添加
org.python.modules.jnios
类,条目如下:
python.modules.builtin = jnios
|
|
name:class
| 需要指定名称和完全限定的类名,用冒号分隔。名称不必与类名相同,它只是Jython用于引用该类的名称。如果名称与现有模块重复,现有模块将被覆盖。例如,将
com.mycompany.python.agent
类添加为
mycompanyagent
,条目如下:
python.modules.builtin = mycompanyagent:org.mycompany.python.agent
|
|
name:null
| 从内置模块列表中移除指定名称的模块。例如,移除
os
模块的条目如下:
python.modules.builtin = os:null
|
需要注意的是,目前使用
-D
命令行开关设置
python.modules.builtin
属性不会将模块添加到内置列表中,必须在注册表文件中设置,或者在
initialize
方法的
post-properties
(第二个参数)中设置。
例如,要将
mymod
模块添加为内置模块,首先编译该模块,并确保它位于正确的目录树中,且在类路径中。然后编辑注册表文件,添加以下内容:
python.modules.builtin = "mymod:org.python.demo.modules.mymod"
添加后,在Jython中可以直接使用
import mymod
导入该模块。这种定义内置模块的机制允许替换现有内置模块或移除内置模块,为扩展和定制Jython提供了便利。
综上所述,Jython与Java的融合为开发者提供了强大的工具,通过嵌入和扩展Jython,可以充分发挥两种语言的优势,实现更灵活、高效的软件开发。无论是在交互式解释器、控制台交互还是模块扩展方面,都有丰富的功能和方法可供使用。开发者可以根据具体需求选择合适的方式,将Jython和Java的优势结合起来,创造出更优秀的应用程序。
```
超级会员免费看

92

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



