深入探索Jython与Java的融合:嵌入与扩展实践
在软件开发领域,将不同编程语言的优势相结合往往能带来更强大的功能和更高的效率。Jython与Java的组合就是这样一种强大的融合,它允许Java应用利用Jython的高级动态特性,同时让Jython应用借助Java的高效字节码。本文将详细介绍如何在Java中嵌入和扩展Jython,以及相关的操作步骤和技术细节。
1. Jython与Java融合的优势
Jython与Java的组合在多语言应用方面具有显著优势。在许多语言中,嵌入和扩展其他语言的功能已经很常见,但Jython与Java的结合在这方面可能超越其他组合。Python的C实现因在语言融合方面的实用性而受到高度赞扬,Jython继承了这一优点,并通过减少语言之间的差异和缝隙,进一步提升了融合效果。在Jython和Java之间传递对象是无缝的,无论是Java对象传递到Jython解释器,还是Jython对象传递到Java,都无需为适应对方语言而对对象进行定制。此外,Jython的嵌入和初始化、对象的设置和获取等操作都非常直观和简单,这些优势使得Jython和Java成为多语言应用的理想组合。
2. 在Java中嵌入Jython
在Java中嵌入Jython有多种用途,特别是在需要交互式命令解释器、生成多样化输出(如报告生成)、采用动态配置以及应用的特定元素需要频繁更改的情况下。虽然嵌入Jython会带来解释器的内存开销和应用中需要额外的类,但它能带来更高的可读性、快速开发和较短的学习曲线等优点。
2.1 嵌入所需的类
org.python.util
包中的三个类为在Java中嵌入Jython提供了必要的手段,它们分别是
PythonInterpreter
、
InteractiveInterpreter
和
InteractiveConsole
,并且它们存在继承关系,后一个类是前一个类的子类。
2.2 PythonInterpreter的使用
在Java应用中嵌入Jython最常用的方法是使用
org.python.util.PythonInterpreter
类的实例。嵌入这个类只需要在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
方法可以完成属性的设置,该方法的签名如下:
public static void initialize(Properties preProperties,
Properties postProperties,
String[] argv)
其中,
preProperties
通常设置为
System.getProperties()
,包含通过Java的
-D
命令行开关设置的属性;
postProperties
通常是一个新的
java.util.Properties
实例,并添加了一些条目;
argv
是一个字符串数组,将成为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的注册表文件(如果找到)。当一个属性在多个地方定义时,属性的优先级顺序为:旧属性(
preProperties
)、注册表属性、新属性(
postProperties
),即新属性会覆盖注册表属性和系统属性。
以下是一个简单的嵌入式
PythonInterpreter
示例:
// 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
}
}
编译这个类的命令如下:
javac -classpath /path/to/jython.jar org/python/demo/Embedding.java
执行该类的命令如下:
java -classpath /path/to/jython.jar:. org.python.demo.Embedding
2.4 实例化解释器
PythonInterpreter
类有三个构造函数:
public PythonInterpreter()
public PythonInterpreter(PyObject dict)
public PythonInterpreter(PyObject dict, PySystemState systemState)
第一个构造函数是无参数的;第二个构造函数接受一个
PyObject
作为参数,该对象将成为解释器的命名空间,通常是
org.python.core.PyStringMap
对象;第三个构造函数允许在实例化解释器时设置命名空间和
PySystemState
对象。
以下是使用第二个构造函数设置命名空间的示例:
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);
2.5 设置输出和错误流
PythonInterpreter
实例有
setOut
和
setErr
方法,用于设置输出流和错误流。这些方法接受
java.io.OutputStream
、
java.io.Writer
或任何类似文件的
org.python.core.PyObject
作为参数。以下是一个将错误消息重定向到文件的示例:
// 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
2.6 PySystemState的使用
在Jython中,
sys
模块包含系统状态信息。在Java中使用
sys
模块需要使用其Java形式
PySystemState
。通过
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
此外,
PySystemState
类还包含
add_package
、
add_classdir
和
add_extdir
三个与类加载相关的方法,这些方法在嵌入Jython时非常重要,因为许多Java应用有自己的类加载器,Jython可能无法正确识别或找到某些Java包。
-
add_package方法:将指定的Java包添加到Jython的包管理器中,示例如下:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_package("com.A.python");
-
add_classdir方法:将指定目录中的包添加到Jython搜索Java包的位置列表中,示例如下:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_classdir("/usr/java/devel/");
-
add_extdir方法:将目录中存档文件的内容添加到Jython搜索Java包的位置列表中,示例如下:
import org.python.core.*;
PySystemState sys = Py.getSystemState();
sys.add_extdir("/usr/java/lib/");
2.7 使用解释器
在解释器中运行代码需要使用
exec
、
execfile
或
eval
方法。
-
exec方法
:允许执行一段Python代码或预编译的代码对象。执行Python代码时,每次调用
exec必须使用完整的、语法正确的语句。以下是一个格式化Jython代码以用于exec方法的示例:
# 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)");
-
execfile方法
:允许执行一个文件或
InputStream。有三个版本的execfile方法:
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
创建一个包含
print 'It worked.'
的Python文件
test.py
,并在主目录中执行以下命令:
java -cp c:\path\to\jython.jar;. org.python.demo.ExecFileTest test.py
-
eval方法
:与
exec方法有三点不同。首先,eval方法总是返回表达式计算的结果,作为org.python.core.PyObject;而exec和execfile方法的返回类型总是void。其次,eval方法目前只接受代码字符串;而exec方法可以接受字符串或预编译的代码对象。最后,eval方法只计算表达式,而exec方法可以执行任意的Jython代码。
以下是使用
eval
方法的示例:
// The interpreter's eval shortcut
interp.eval("1 or 0")
// The built-in version
__builtin__.eval(new PyString("1 or 0"), interp.getLocals())
3. 编译代码对象以供后续使用
在Jython解释器中使用字符串调用
exec
和
eval
方法时,解释器会先编译代码,然后执行。如果应用中有大量的Jython代码或代码需要多次使用,可以在应用的非关键时间部分编译代码,以节省时间。使用Jython的内置
compile
函数可以创建编译后的代码对象。
以下是创建一个用于
exec
方法的
PyCode
对象的示例:
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);
4. 处理解释器中的异常
如果解释器中出现问题,会抛出
org.python.core.PyException
异常。在Java中捕获Jython异常时,无法直接区分具体的Jython异常类型。可以在Jython解释器对象中使用
try/except
语句,或者在Java代码中避免Jython异常,也可以使用
Py.matchException
方法在Java的
catch
块中辨别实际的异常类型。
以下是在Jython和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();
}
5. set和get方法
PythonInterpreter
的
set
和
get
方法分别用于在解释器的本地命名空间中设置和获取对象。
-
set方法
:
PythonInterpreter类有两个set方法:
public void set(String name, Object value)
public void set(String name, PyObject value)
如果第二个参数是Java对象,解释器会将其转换为适当的Jython类型;如果是
PyObject
,则直接放入命名空间字典中。
以下是设置简单对象的示例:
interp.set("S", "String literals becomes a PyStrings in the interp");
interp.set("V", java.util.Vector());
对于基本类型,需要使用其
java.lang
对应的类,例如:
interp.set("I", new Integer(1));
interp.set("C", new Character('c'));
-
get方法
:
PythonInterpreter的get方法有两个签名:
public PyObject get(String name)
public Object get(String name, Class javaclass)
第一个
get
方法只需要名称参数,返回一个
PyObject
;第二个
get
方法需要指定返回对象的类。
以下是使用第二个
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
6. __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__
方法的示例:
// 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);
}
}
}
7. getLocals和setLocals方法
getLocals
和
setLocals
方法分别用于获取和设置解释器的整个命名空间映射对象。这两个方法的签名如下:
public PyObject getLocals()
public void setLocals(PyObject d)
以下是一个使用
setLocals
和
getLocals
方法的示例:
// 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
8. imp和顶级脚本
许多Jython模块使用顶级脚本环境来有条件地运行主代码部分。当嵌入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
9. 嵌入InteractiveInterpreter
InteractiveInterpreter
是
PythonInterpreter
的子类,提供了
runcode
、
runsource
和
interrupt
方法,用于更高级的交互。该类的两个重要行为是异常捕获和语句完整性管理。
以下是
InteractiveInterpreter
类的方法总结:
| 方法签名 | 总结 |
| — | — |
|
public InteractiveInterpreter()
public InteractiveInterpreter (PyObject locals)
| 构造函数,第二个构造函数接受一个
PyStringMap
对象 |
|
public Boolean runsource(String source)
public Boolean runsource(String source, String filename)
public Boolean runsource(String source, String filename, String symbol)
| 尝试编译和执行代码字符串,显示异常信息但不传播异常。返回值:成功 -> false;异常发生 -> false;语句不完整 -> true |
|
public void runcode(PyObject code)
| 执行代码对象并显示执行过程中发生的任何异常 |
|
public void showexception(PyException exc)
| 将异常信息写入
sys.stderr
,主要用于内部使用 |
|
public void write(String data)
| 将字符串写入
sys.stderr
,主要用于内部使用 |
|
public void interrupt(ThreadState ts) throws InterruptedException
| 以线程友好的方式暂停代码以插入异常 |
以下是一个嵌入
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();
}
}
}
}
}
编译该类的命令如下:
javac -classpath \path\to\jython.jar;.
org\python\demo\InteractiveEmbedding.java
10. 嵌入InteractiveConsole
InteractiveConsole
在嵌入的基础上增加了一层抽象,专门用于Jython中常见的控制台交互。创建控制台交互需要使用
InteractiveConsole
的三个方法:
interact
、
raw_input
和
push
。
以下是
InteractiveConsole
类的方法总结:
| 方法 | 总结 |
| — | — |
|
public InteractiveConsole()
public InteractiveConsole (PyObject locals)
public InteractiveConsole (PyObject locals, String filename)
| 构造函数,允许可选地设置解释器的本地命名空间和用于错误消息的文件名 |
|
public void interact()
public void interact(String banner)
| 模拟Jython解释器,可选的
banner
参数是在第一次交互前打印的消息 |
|
public boolean push(String line)
| 将不以
\n
结尾的单行代码推送到解释器中,返回值与
InteractiveInterpreter
的
runsource
方法相同 |
|
public String raw_input(PyObject prompt)
| 与内置的
raw_input
方法相同 |
以下是一个嵌入
InteractiveConsole
的示例:
// 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");
}
}
总结
通过以上内容,我们详细介绍了如何在Java中嵌入和扩展Jython。嵌入Jython可以让Java应用利用Jython的动态特性,而扩展Jython则允许我们用Java编写Jython模块。无论是嵌入还是扩展,都需要注意一些关键步骤,如初始化环境、实例化解释器、处理异常、设置命名空间等。希望本文能帮助你更好地理解和应用Jython与Java的融合,为你的软件开发带来更多的可能性。
深入探索Jython与Java的融合:嵌入与扩展实践
11. 扩展Jython
扩展Jython意味着用Java编写Jython模块。这里的Jython模块指的是那些特别表现得像Jython模块的Java类。与普通的Java类编写有所不同,当一个类需要支持Jython的特性,如有序和关键字参数时,就需要特别处理。
11.1 一个用Java编写的Jython模块示例
以下是一个简单的Jython模块示例,它展示了创建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]);
}
}
运行该模块的示例输出如下:
>>> 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模块在性能上可能更具优势。
11.2 ClassDictInit接口
一个用Java类实现的Jython模块可以通过实现
ClassDictInit
接口来控制模块在Jython中可见的属性名称和实现。实现该接口的类必须有一个
classDictInit
方法:
public static void classDictInit(PyObject dict)
Jython在类初始化时会调用
classDictInit
方法,从而可以控制在Jython中可见的属性名称及其实现。例如,
mymod
类使用
classDictInit
方法设置
__doc__
字符串并从Jython可见的名称中移除
classDictInit
方法。
另外,还有一种控制Java方法在Jython中可见性的方法,即通过声明抛出
org.python.core.PyIgnoreMethodTag
异常。任何声明抛出该异常的Java方法会自动从Jython的视图中移除,但这种方法目前还处于实验阶段。
11.3 doc 字符串
可以通过包含一个名为
__doc__
的静态
PyString
成员来定义模块的文档字符串:
public static __doc__ = new PyString("Some documentation");
类中的静态方法在Jython中会成为模块函数。例如,
mymod
类中的静态
fibTest
方法在Jython模块中表现得像
fibTest
函数。为该函数添加文档字符串可以通过添加一个名为
__doc__fibTest
的静态
PyString
来实现。
11.4 异常处理
在Java中引发Jython异常需要抛出
PyException
类,并传递两个参数:实际的Jython异常和异常消息。Jython的内置异常位于
org.python.core.Py
类中。例如,在Jython中
raise ValueError, "Invalid Value"
在Java中的形式为:
throw new PyException(Py.ValueError, "Invalid Value");
mymod
类在
fibTest
方法中使用
PyException
抛出Jython的
ValueError
异常。
11.5 参数处理
Jython函数具有丰富的参数方案,包括有序参数、关键字参数、默认值和通配符。在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)
每个构造函数的第一个参数是函数名,第二个和第三个参数分别是
PyObject[] args
和
String[] kw
,其余参数是方法期望的参数列表。
以下是一些Jython方法及其Java实现和
ArgParser
的对应示例:
| Jython方法 | Java实现 |
| — | — |
|
def test(A, B, C=2)
|
public static PyObject test(PyObject[] args, String[] kws) { ArgParser ap = new ArgParser("test", args, kws, "A", "B", "C"); }
|
|
def addContact(name, addr, ph=None)
|
public static PyObject addContact(PyObject[] args, String[] kws) { ArgParser ap = new ArgParser("addContact", args, kws, new String[] {"name", "addr", "ph"}); }
|
可以使用
ArgParser
实例的
get*
方法检索参数值,例如:
public static PyObject test(PyObject[] args, String[] kws) {
ArgParser ap = new ArgParser("test", args, kws, "A", "B", "C");
int A = ap.getInt(0);
String B = ap.getString(1);
int C = ap.getInt(2, 2); // 获取默认值为2的参数
}
12. 在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");
13. 处理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)。
例如,导入
random
模块并使用
randint
方法时,不能直接调用,而应该使用
__findattr__
方法结合
__call__
方法:
PyObject random = __builtin__.__import__("random");
random.__findattr__("randint").__call__(new PyInteger(10),
new PyInteger(20));
invoke
方法是调用
PyObject
方法的快捷方式,其不同的方法签名如下:
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
方法创建
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 key;
for (int i = 0; (key = keys.__finditem__(i)) != null; i++) {
System.out.println("K: " + key + ", V: " + dct.__getitem__(key));
}
14. 用Java编写Jython类
用Java模拟Jython类时,最好的方法是继承
org.python.core
包中最适合的
Py*
类,并实现所需类型类的所有特殊方法。所有用Java编写的类都可以继承
PyObject
并实现
__findattr__
、
__setattr__
和
__delattr__
方法。对于映射对象,应该继承
PyDictionary
或
PyStringMap
并实现
__finditem__
、
__setitem__
、
__delitem__
及相关的映射方法。
15. 将Java类作为内置Jython模块添加
编写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
导入该模块。
总结
通过全面深入地探讨在Java中嵌入和扩展Jython的各个方面,我们了解到这种融合为软件开发带来了诸多优势。嵌入Jython能让Java应用借助其高级动态特性,实现交互式命令解释器、多样化输出生成等功能;而扩展Jython则允许用Java编写高效的Jython模块,提升性能。
在实际应用中,需要注意初始化环境、实例化解释器、处理异常、设置命名空间等关键步骤。同时,掌握Jython和Java之间对象传递、方法调用的特殊方式,以及如何利用特殊方法和接口来实现特定功能也非常重要。
希望本文能为开发者提供有价值的参考,帮助大家更好地运用Jython与Java的融合,开发出更强大、更灵活的软件系统。在未来的开发过程中,大家可以根据具体需求,灵活运用这些技术,不断探索和创新。
超级会员免费看
95

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



