73、深入探索Jython与Java的融合:嵌入与扩展实践

深入探索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的融合,开发出更强大、更灵活的软件系统。在未来的开发过程中,大家可以根据具体需求,灵活运用这些技术,不断探索和创新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值