72、深入探索Jython与Java的融合:嵌入与扩展的艺术

深入探索Jython与Java的融合:嵌入与扩展的艺术

在软件开发领域,多语言融合是一种强大的技术手段,它能让开发者充分发挥不同语言的优势。Jython与Java的结合就是这样一个绝佳的例子,它们的融合为开发者带来了诸多便利和可能性。

1. Jython与Java融合的魅力

Jython与Java的结合具有显著优势。Java应用可以借助Jython的高级动态特性,通过嵌入解释器轻松实现功能扩展;而Jython应用则能包含用Java编写的模块,在不影响模块功能的前提下,利用Java高效的字节码提升性能。这种嵌入和扩展的实现方式,使得在单一应用中融合多种语言变得更加容易。

与其他语言的融合方式相比,Jython - Java组合表现更为出色。Python的C实现以其在语言融合方面的实用性而备受赞誉,Jython不仅继承了这一优点,还通过减少语言间的差异,实现了Java和Jython对象的无缝传递。无论是Java对象传入解释器,还是Jython对象传出到Java,都无需为了适应对方语言而对对象进行定制,这种无缝集成使得Jython和Java成为多语言应用的理想组合。

2. 嵌入Jython到Java

嵌入Jython到Java有诸多用途,例如需要交互式命令解释器、生成多样化输出(如报告)、采用动态配置,以及应用中的特定元素需要频繁更改等场景。嵌入Jython还能带来提高代码可读性、实现快速开发、缩短学习曲线等好处,不过也存在解释器内存开销和需要额外类的缺点。

org.python.util 包中有三个类可用于在Java中嵌入Jython,分别是 PythonInterpreter InteractiveInterpreter InteractiveConsole ,它们呈层级关系,后一个类是前一个类的子类。

2.1 PythonInterpreter的使用

嵌入Jython最常见的方式是在Java应用中使用 org.python.util.PythonInterpreter 类的实例。要在Java中嵌入该类,只需添加两行代码:

import org.python.util.PythonInterpreter; 
PythonInterpreter interp = new PythonInterpreter(); 

这里的 interp 可看作是Java中的一个容器,Jython解释器在这个容器中运行,容器内外的代码相互隔离。

2.2 初始化Jython环境

Jython环境依赖于一些属性,如 python.home python.path python.security.respectJavaAccessibility 等,这些属性通常在启动时从注册表文件加载或通过命令行选项设置。在Java中嵌入Jython时,可以在创建解释器对象之前显式设置这些属性,这通过 PythonInterpreter 类的静态 initialize 方法实现。

initialize 方法的签名如下:

public static void initialize(Properties preProperties, 
                              Properties postProperties, 
                              String[] argv) 

参数可理解为“旧属性”、“新属性”和“命令行参数”。旧属性通常设置为 System.getProperties() ,包含通过Java的 -D 命令行开关设置的属性;新属性通常是一个新的 java.util.Properties 实例,并添加了一些条目;第三个参数是一个字符串数组,成为Jython的 sys.argv

以下是设置 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) {
        Properties postProps = new Properties(); 
        Properties sysProps = System.getProperties(); 
        if (sysProps.getProperty("python.home")==null) 
            sysProps.put("python.home", "c:\\jython 2.1a1"); 
        Enumeration e = sysProps.propertyNames(); 
        while ( e.hasMoreElements() ) {
            String name = (String)e.nextElement(); 
            if (name.startsWith("python.")) 
                postProps.put(name, System.getProperty(name)); 
        } 
        PythonInterpreter.initialize(sysProps, postProps, argv); 
        interp = new PythonInterpreter(); 
    } 
    public void test() {
        interp.exec("import sys"); 
        interp.exec("print"); 
        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"); 
    } 
} 

编译和执行该类的命令如下:

javac -classpath /path/to/jython.jar org/python/demo/Embedding.java 
java -classpath /path/to/jython.jar:. org.python.demo.Embedding 
2.3 实例化解释器

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; 
PyStringMap dict = new PyStringMap(); 
dict.__setitem__("Name", new PyString("Strategy test")); 
dict.__setitem__("Context", Py.java2py(new SomeContextClassOrBean())); 
PythonInterpreter interp = new PythonInterpreter(dict); 

使用第三个构造函数设置 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")); 
interp = new PythonInterpreter(dict, sys); 

如果嵌入多个解释器,它们会共享系统状态,但每个解释器的本地命名空间是唯一的。

2.4 设置输出和错误流

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() {
        try {
            interp.setErr(new FileWriter(new File("errors"))); 
        } catch (IOException e) {
            e.printStackTrace(); 
        } 
        interp.exec("assert 0, 'This should end up in a file.'"); 
    } 
} 

编译和执行该类后,控制台只会显示 Exception in thread "main" ,其余的回溯信息会出现在 errors 文件中。

2.5 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"); 
        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"); 
    } 
} 

编译和执行该类后,可以看到 sys.path 的变化。

Jython的 PySystemState 类还包含三个与类加载相关的方法: add_package add_classdir add_extdir 。这些方法在嵌入Jython时非常重要,因为当应用的类加载器与Jython的不同时,Jython可能无法正确识别或找到某些Java包。

  • add_package 方法:将指定的Java包添加到Jython的包管理器中,示例如下:
import org.python.core.*; 
PySystemState sys = Py.getSystemState(); 
sys.add_package("com.A.python"); 
  • add_classdir 方法:使指定目录中的包可用,示例如下:
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/"); 
3. 使用解释器

在解释器中运行代码需要使用解释器的 exec execfile eval 方法。

3.1 exec方法

exec 方法允许执行一段Python代码字符串或预编译的代码对象。执行字符串时,每次调用 exec 都必须使用完整的、语法正确的语句。以下是一个简单函数定义在Jython和Java中的不同写法:

# 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)"); 
3.2 execfile方法

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); 
        interp.execfile(filename); 
        try {
            FileInputStream s = new FileInputStream(filename); 
            interp.execfile(s); 
        } catch (FileNotFoundException e) {
            e.printStackTrace(); 
        } 
        try {
            FileInputStream s = new FileInputStream(filename); 
            interp.execfile(s, sys.argv.__getitem__(0).toString()); 
        } catch (FileNotFoundException e) {
            e.printStackTrace(); 
        } 
    } 
} 

编译和执行该类后,可以看到文件被执行三次。

3.3 eval方法

eval 方法与 exec 方法有三点不同:
- eval 方法总是返回表达式的计算结果作为 org.python.core.PyObject ,而 exec execfile 的返回类型总是 void
- eval 方法目前只接受代码字符串,而 exec 方法可以接受字符串或编译后的代码对象。
- eval 方法只计算表达式,而 exec 方法可以执行任意Jython代码。

以下是使用 eval 方法的示例:

interp.eval("1 or 0") 
__builtin__.eval(new PyString("1 or 0"), interp.getLocals()) 

eval 方法是对Jython内置 eval 方法的封装,使用时需要注意表达式不能包含赋值或语句。

4. 编译代码对象以供后续使用

如果应用中有大量的Jython代码或需要多次使用相同的代码,为了避免每次执行都进行编译,可以使用Jython的内置 compile 函数预先编译代码。

使用 compile 函数创建编译后的代码对象的步骤如下:

import org.python.core.*; 
String s = "print 'Hello World'"; 
PyCode code = __builtin__.compile(s, "<>", "exec"); 
interp.exec(code); 

compile 函数需要三个参数:代码字符串、文件名(用于错误信息)和模式(可以是 exec eval single )。

5. 解释器中的异常处理

当解释器中出现问题时,会抛出 org.python.core.PyException 异常。在Java中捕获该异常后,由于无法直接区分具体的Jython异常类型,可以采取以下几种处理方式:
- 在解释器中使用 try/except 语句,示例如下:

interp.exec("try:\n" + 
            "    _file = open(" + filename + ")\n" + 
            "except:\n" + 
            "    print 'File not found. Try again.'); 
  • 在Java代码中进行错误处理,示例如下:
File file = new File(filename); 
if (file.canRead()!=true) {
    System.out.println("File not found. Try again."); 
    break; 
} 
interp.exec("file = open(" + filename + ")"); 
  • 使用 Py.matchException 函数在Java的 catch 块中区分具体的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对象,解释器会将其转换为合适的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')); 
6.2 get方法

PythonInterpreter get 方法有两个签名:

public PyObject get(String name) 
public Object get(String name, Class javaclass) 

第一个方法只需要名称参数,返回 PyObject ;第二个方法需要指定返回对象的类。自动类型转换在 set 方法中是单向的,要获取Java对象,需要指定Java类并进行强制类型转换,示例如下:

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), " + 
                    "'------------'"); 
        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); 
        interp.exec("pyClass = str(testObject.__class__) \n" + 
                    "print o[o.rfind('.') + 1:].ljust(20), " + 
                    "pyClass[pyClass.rfind('.') + 1:].ljust(20), " + 
                    "n[n.rfind('.') + 1:]"); 
    } 
} 

编译和运行该类后,可以看到对象在Java、Jython和Java中的类型转换情况。

7. __tojava__方法

Jython类的实例有一个特殊的 __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); 

为了避免 ClassCastException ,可以在转换前检查是否返回 Py.NoConversion 对象,示例如下:

// 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() {
        interp.exec("test = 'A'"); 
        PyObject retrievedObject = interp.get("test"); 
        Object myObject = retrievedObject.__tojava__(Character.class); 
        if (myObject == Py.NoConversion) {
            System.out.println("Unable to convert."); 
        } else {
            Character myChar = (Character)myObject; 
            System.out.println("The Character is: " + myChar); 
        } 
    } 
} 
8. getLocals和setLocals方法

getLocals setLocals 方法用于设置或获取解释器的整个命名空间映射对象。 getLocals 方法返回一个字典状的 PyObject ,通常是 org.python.core.PyStringMap 对象; setLocals 方法需要一个这样的对象作为参数。

以下是一个使用 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); 
    } 
} 

编译和运行该类后,可以看到命名空间中的对象。

9. 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(); 
        PyModule mod = imp.addModule("__main__"); 
        interp.setLocals(mod.__dict__); 
        interp.execfile(filename); 
    } 
} 

编译和运行该类后,可以看到脚本中的 __name__ == '__main__' 条件被满足。

10. 嵌入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) | 尝试编译和执行代码字符串,显示异常信息但不传播异常,返回值表示语句是否完整 |
| 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) {
        if (System.getProperty("python.home") == null) 
            System.setProperty("python.home", "c:\\jython-2.1"); 
        InteractiveInterpreter.initialize(System.getProperties(), null, argv); 
        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(); 
                } 
            } 
        } 
    } 
} 

编译和运行该类后,可以进行交互式操作。

11. 嵌入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 的单行代码推送到解释器,返回值表示是否需要更多输入 |
| 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() {
        if (System.getProperty("python.home") == null) 
            System.setProperty("python.home", "c:\\jython-2.1"); 
        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"); 
    } 
} 

编译和运行该类后,将显示Jython的交互式控制台。

Jython与Java的融合提供了丰富的功能和灵活的编程方式。通过嵌入Jython到Java,开发者可以充分利用Jython的动态特性和Java的高效性能;通过扩展Jython,开发者可以用Java编写Jython模块,满足特定的设计需求。无论是在交互式开发、报告生成还是模块定制方面,Jython与Java的结合都展现出了强大的优势。在实际开发中,开发者可以根据具体需求选择合适的方法,实现多语言的无缝融合。

12. 扩展Jython

扩展Jython意味着用Java编写Jython模块,这里的Jython模块指的是那些行为类似于Jython模块的Java类。与普通的Java类不同,真正的Jython模块需要支持Jython的特性,如有序和关键字参数。

12.1 编写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模块的多个方面,包括 ClassDictInit 接口、 classDictInit 方法、静态方法修饰符、 __doc__ 字符串和 PyException 的使用。

12.2 ClassDictInit接口

实现 ClassDictInit 接口的Java类可以控制在Jython中可见的属性名及其实现。 classDictInit 方法在类初始化时被Jython调用,例如 mymod 类使用该方法设置 __doc__ 字符串并移除 classDictInit 方法本身。不过,对于简单的 __doc__ 字符串定义,也可以直接使用静态字符串成员:

public static String __doc__="Test class to confirm builtin module"; 

此外,还可以通过 org.python.core.PyIgnoreMethodTag 异常来控制Java方法在Jython中的可见性,声明抛出该异常的Java方法会自动从Jython的视图中移除。

12.3 __doc__字符串

可以通过包含一个名为 __doc__ 的静态 PyString 成员来定义模块的文档字符串。对于模块中的静态方法,如 fibTest ,可以通过添加名为 __doc__fibTest 的静态 PyString 成员来为其添加文档字符串,这样在Jython中可以通过 mymod.fibTest.__doc__ 来获取该方法的文档。

12.4 异常处理

在Java中抛出Jython异常需要使用 PyException 类,并传入两个参数:实际的Jython异常和异常消息。例如,在Jython中抛出 ValueError 异常的语句 raise ValueError, "Invalid Value" ,在Java中可以表示为:

throw new PyException(Py.ValueError, new PyString("Invalid Value")); 

mymod 类的 fibTest 方法中就使用了 PyException 来抛出Jython的 ValueError 异常。

12.5 参数处理

Jython函数具有丰富的参数方案,包括有序参数、关键字参数、默认值和通配符。在Java中可以通过以下方法来处理位置和关键字参数:

public PyObject MyFunction(PyObject[] args, String[] kw); 

org.python.core.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) 

以下是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 String getString(int pos) 
public String getString(int pos, String def) 
public int getInt(int pos) 
public int getInt(int pos, int def) 
public PyObject getPyObject(int pos) 
public PyObject getPyObject(int pos, PyObject def) 
public PyObject getList(int pos) 
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__ 方法。

14.1 __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)); 
14.2 invoke方法

invoke 方法用于在Java中调用 PyObject 的方法,它是调用Jython映射对象方法的通用方式,其不同的方法签名如下:

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 __len__ 方法可能返回错误的值,因此需要通过测试每个索引值直到遇到不存在的索引来安全地遍历Jython序列:

PyObject key; 
for (int i = 0; (key = keys.__finditem__(i)) != null; i++) {
    System.out.println("K: " + key + ", V: " + dct.__getitem__(key)); 
} 
15. 用Java编写Jython类

当Java类模拟Jython模块时,模块中的函数通常实现为静态类成员。要在Java中模拟Jython类,可以使用静态内部类,也可以通过实现 ClassDictInit 接口获得更多的灵活性。

更好的做法是继承 org.python.core 包中最合适的 Py* 类,并实现所需类型类的所有特殊方法。所有用Java编写的类都可以继承 PyObject 并实现 __findattr__ __setattr__ __delattr__ 方法;映射对象可以继承 PyDictionary PyStringMap 并实现 __finditem__ __setitem__ __delitem__ 和相关的映射方法。

16. 将Java类作为内置Jython模块添加

在Java中编写Jython模块后,可以将其指定为内置模块。通过 python.modules.builtin 注册表键可以将模块添加到内置模块列表中,该属性是一个逗号分隔的列表,模块条目有三种形式:
| 条目语法 | 描述 |
| — | — |
| name | 仅Java类的名称,假设该类位于 org.python.modules 包中,添加该条目后可以在Jython中使用 import name 导入 |
| name:class | 需要一个名称和完全限定的类名,用冒号分隔,名称可以是任意合法标识符,类名必须是唯一标识类的完整包名.类名,该名称会覆盖已有的同名模块 |
| name:null | 从内置模块列表中移除指定名称的模块 |

需要注意的是,目前使用 -D 命令行开关设置 python.modules.builtin 属性不会将模块添加到内置列表中,必须在注册表文件或 initialize 方法的 post-properties (第二个参数)中设置。

例如,要将 mymod 作为模块添加,首先编译该类并确保其位于正确的目录树中且在类路径中,然后编辑注册表文件,添加以下内容:

python.modules.builtin = "mymod:org.python.demo.modules.mymod" 

添加后,在Jython中可以直接使用 import mymod 导入该模块。

总结

Jython与Java的融合为开发者提供了强大的工具,通过嵌入Jython到Java,开发者可以利用Jython的动态特性和高级功能,同时借助Java的高效性能和丰富的类库。在嵌入过程中,需要注意解释器的初始化、实例化、代码执行、异常处理以及对象的传递和类型转换等方面。而扩展Jython则允许开发者用Java编写Jython模块,实现更复杂的功能和更好的性能优化。

在实际开发中,开发者可以根据具体的需求选择合适的嵌入或扩展方式。例如,在需要交互式开发、动态配置或快速原型开发时,可以选择嵌入Jython;而在需要提高性能、实现复杂算法或与现有Java系统集成时,可以考虑扩展Jython。通过合理运用Jython与Java的融合技术,开发者可以创建出更加灵活、高效和强大的应用程序。

流程图:Jython与Java融合流程

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{选择操作类型}:::decision
    B -->|嵌入Jython| C(初始化Jython环境):::process
    C --> D(实例化解释器):::process
    D --> E(设置输出和错误流):::process
    E --> F(使用解释器执行代码):::process
    F --> G(处理异常和对象转换):::process
    G --> H([结束嵌入]):::startend
    B -->|扩展Jython| I(编写Jython模块):::process
    I --> J(控制模块属性和文档):::process
    J --> K(处理参数和异常):::process
    K --> L(将模块作为内置模块添加):::process
    L --> M([结束扩展]):::startend

通过以上的介绍和示例,相信开发者对Jython与Java的融合有了更深入的了解,能够在实际项目中灵活运用这些技术,实现多语言开发的优势互补。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值