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

深入探索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的优势结合起来,创造出更优秀的应用程序。
```

内容概要:本文档介绍了基于3D FDTD(时域有限差分)方法在MATLAB平台上对微带线馈电的矩形天线进行仿真分析的技术方案,重点在于模拟超MATLAB基于3D FDTD的微带线馈矩形天线分析[用于模拟超宽带脉冲通过线馈矩形天线的传播,以计算微带结构的回波损耗参数]宽带脉冲信号通过天线结构的传播过程,并计算微带结构的回波损耗参数(S11),以评估天线的匹配性能和辐射特性。该方法通过建立三维电磁场模型,精确求解麦克斯韦方程组,适用于高频电磁仿真,能够有效分析天线在宽频带内的响应特性。文档还提及该资源属于一个涵盖多个科研方向的综合性MATLAB仿真资源包,涉及通信、信号处理、电力系统、机器学习等多个领域。; 适合人群:具备电磁场微波技术基础知识,熟悉MATLAB编程及数值仿真的高校研究生、科研人员及通信工程领域技术人员。; 使用场景及目标:① 掌握3D FDTD方法在天线仿真中的具体实现流程;② 分析微带天线的回波损耗特性,优化天线设计参数以提升宽带匹配性能;③ 学习复杂电磁问题的数值建模仿真技巧,拓展在射频无线通信领域的研究能力。; 阅读建议:建议读者结合电磁理论基础,仔细理解FDTD算法的离散化过程和边界条件设置,运行并调试提供的MATLAB代码,通过调整天线几何尺寸和材料参数观察回波损耗曲线的变化,从而深入掌握仿真原理工程应用方法。
内容概要:本文系统介绍了无人机测绘在多个领域的广泛应用,重点阐述了其在基础地理信息测绘、工程建设、自然资源生态环境监测、农业农村管理、应急救灾以及城市管理等方面的实践价值。无人机凭借灵活作业、低成本、高精度和快速响应的优势,结合航测相机、LiDAR、多光谱、热成像等多种传感器,能够高效获取DOM、DSM、DEM、DLG等关键地理数据,并生成三维模型,显著提升测绘效率精度,尤其适用于复杂地形和紧急场景。文章还强调了无人机在不同时期工程项目中的动态监测能力及在生态环保、土地确权、灾害应急等方面的数据支撑作用。; 适合人群:从事测绘、地理信息系统(GIS)、城乡规划、自然资源管理、农业信息化、应急管理等相关工作的技术人员管理人员;具备一定地理信息基础知识的专业人员;无人机应用从业者或爱好者。; 使用场景及目标:①了解无人机测绘的技术优势及其在各行业中的具体应用场景;②为实际项目中选择合适的无人机测绘方案提供参考依据;③支持政府部门、企事业单位在土地管理、工程建设、灾害应对等领域实现数字化、智能化决策。; 阅读建议:此资源以应用为导向,涵盖了技术原理实践案例,建议结合具体业务需求深入研读,并可进一步索取“无人机测绘设备选型作业流程清单”以指导实际操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值