alt.lang.jre: 了解 Jython

来源:岁月联盟 编辑:zhuzhu 时间:2008-12-11

  欢迎来到 alt.lang.jre 新系列。虽然本系列的大多数读者熟悉 Java 语言以及它是如何在跨平台虚拟机(JRE 的一部分)上运行的,但是只有少数人知道 JRE 可以允许 Java 语言之外的语言。因为对多语言的支持(包括一些在 Java 平台出现之前存在的语言),JRE 为具有其他背景的开发人员进入 Java 平台提供了一个很轻松的入口点。它还使那些很少(如果有的话)使用 Java 以外的其他语言的开发人员有机会探索其他语言的潜在能力,而不用担心进入不熟悉的内部环境。

  为不同的开发场景选择正确语言的能力对于任何开发人员来说都是巨大的好处,而对于那些处理复杂企业系统的人来说尤其如此。类似地,JRE 对多语言的适应性巩固了 Java 平台作为企业开发环境的地位。

  本系列探讨了 JRE 的许多种替代语言。在本系列中学到的大多数语言是开放源代码并可以免费使用,不过有少数是必须购买的商业产品。许多语言具有“脚本”语言的特点,通过牺牲一些计算机效率,更多地提升了程序员的生产率,另一些属于流行的主流语言。不论是哪种情况,它们都得到 JRE 支持,并且作者相信它们可以增强 Java 平台的动态和灵活本性。

  在本系列的第一期中,我将侧重于 Jython,这是一种结合了 Python 语言的易用性和 Java Runtime Environment 的强大和灵活性的脚本语言。我选择 Jython 作为本系列介绍的第一种语言是因为在我看来,它是 JRE 的替代语言中最成熟和生产率最高的。因此,我对 Jython 的探索将集中于它为 Java 编程工作带来的生产率提高。本系列后面的几期将介绍 Groovy、JRuby、Rhino、Nice 和 NetRexx,以及其他许多令人激动的 JRE 替代语言。

  Jython 的情况

  Jython 是 Python 的一种实现,它在 JVM 上运行时完全支持 Python 2.1 语法和大部分语义。它是一种解释性的、交互式的、面向对象的编程语言,用 100% 的纯 Java 编写并无缝地与 Java 平台集成。因此,Jython 非常容易学习和使用,而且可以运行在任何支持 JVM 的平台上——包括 Microsoft Windows、Mac OS 和大多数 Unix 和 Linux 变体。Jython 还可以完全利用 Java Runtime Environment 提供的所有类和 API。

  Jython 异常高的生产率在于它结合了脚本语言 Python 的易用性与 Java 平台的健壮性。它的简单、类似英语的语法和直观的语义使 Jython 成为现有 Python 程序员、初级程序员或寻找非常快速的应用程序开发平台的编程员的一种理想语言。它的字符串和文本支持功能使它成为编写文本处理程序的最适合平台。而其内置的字符串操纵函数(包括正则表达式),以及程序上的特性和方便性使它成为创建简单“脚本”程序的理想语言。

  与 Visual Basic 相比,Jython 更容易入门并具有更好的适应性,与许多脚本语言相比,它更注重功能,与 Java 语言相比,它具有更强的能力并更可靠。它既是一种强壮的函数型程序设计语言,又是(由于其面向对象和模块化的体系结构)开发大型应用程序的出色语言。

  在下面几节中,我将详细分析这种强大和灵活的语言背后的机制。

  Jython 的功能

  Jython 语法是面向行的(line-oriented),并且类似于英语,同时完全支持结构化和面向对象类型的语言,如 Java 和 C++。Jython 代码可以直接解释,也可以翻译成自动编译为类文件的 Java 源文件(请参阅 参考资料)。编译的 Jython 类可以独立于 Jython 解释器执行,也可以在 Java 代码中使用,就像它们是 Java 类一样(例如,开发 applet 或者 servlet)。

  如前所述,Jython 的生产率是它最出色的优点。下面是 Jython 的一些独特功能(与 Java 语言相比):

  可以用交互模式一行一行地输入代码并运行,这可以改进学习并促进试验性使用。

  变量是在赋值时动态键入的,因此不需要预先声明变量或者使用 new 运算符。这些增强的功能可以显著提高编程的灵活性,还会降低程序的正文篇幅大小,因而提高了生产率。

  可以广泛地、方便地使用字符串运算(例如,在 Jython 中 x == y 与 Java 语言中的 x.equals(y) 是一样的)。

  Adapter 对象是自动生成的并自动提供默认的 Java 接口实现,使得更容易实现事件处理程序回调。

  自然的精度不定整数值(Natural indefinite precision integer value) (即对 java.math.BigInteger 之类类型的繁琐访问)和自然复杂值( natural complex values)使得 Jython 容易地支持许多比 Java 语言所能支持的更为精确的应用程序。

  动态特性(每个作用类似 Java Map 的类实例)使得类功能可以更为动态。

  增强的导入(像 Java 1.5 的 import static )提高了灵活性和模块性。

  Classless 和 mainless (即开放)代码提高了程序式程序(如脚本)的生产率。

  每个源文件中有多个公共变量、函数和/或类,降低了必须维护的源文件数量,这简化了大规模开发。

  函数和方法的默认参数极大地减少了需要创建的重载方法数量。

  函数和方法的 Keyword 参数提高了代码的自解释性和灵活性。

  函数型程序设计(函数是第一级对象)提供了非常强大和灵活的编程样式,这是 Java 语言缺少的。

  如果需要,多重继承支持更丰富的继承层次结构。

  运算符重载允许创建与语言无缝集成的新数据类型。

  这个列表给出了有关 Jython 结构上实用性的很好概览。在下面几节中,我将描述一些构成 Jython 生产率优势的元素——即数据类型和语句类型。

  第一级类数据类型

  Jython 支持许多数据类型。它们都是第一级类,并且都是对象。大多数类型支持字面表示,这极大地简化了它们的使用。许多比 Java 类型更容易使用,特别是集合类型。表 1 中描述了最常用的 Jython 数据类型。

  表 1. Jython 数据类型总结

类型 说明 示例 Null 一个未定义的值(就像 Java 语言中的 null ) None Integer 一个标准 (Java 语言中的 int )或者长 (Java 语言中的 BigInteger )整数 -1 0 100

 

-1L 1000000000000000000000000000000000000000000001L Float 一个分数或者指数 (像 Java 语言中的 double ) 1.0
-1e6 0.000001e-10 1E11
Complex 一对支持复数算术的浮点数 1j -10J
2-3j 1e6j
String 一个不可变字符序列。没有字符类型(用单字符字符串代替) "a string"
'a string'
"""a long (possibly multi-line) string"""
'''another (possibly multi-line) long string'''
r'''^xxx(.*?).yyy(.*?).zzz(.*?).$'''
'x' "y" 'n' ""
Range 一个不可变整数序列 range(10)
range(10,0,-1)
xrange(1,100000,10)
Tuple 一个不可变的任意类型序列(像 java.util.Collections.unmodifiableCollection(someList) ) () (1,) (1,2,3)
(1, "mixed", 2, "tuple")
((1,2),(3,4))
List 一个可变的任意类型的序列(像 java.util.ArrayList ) [] [1] [1,2,3]
[1, 'mixed', 2, 'list']
[[1,2],[3,4]]
Map 一个可变的项集合(名称/值对)(像 java.util.Map )。名称必须是不可变并且惟一的,值可以是 None {}
{1:'one',2:'two',3:'three'}
{"one";1, "two":2, "three":3}
{"x":1, "ints":(1,2,3),
"name":{"first":"Barry", last":"Feigenbaum"}}
Boolean 基于其他类型的 true/false 值 false - 0 或 empty:None
0 0.0
() []

 

  true - 非 0,非 empty:1 -1 0.0001
[1] [1,2,3] {"greeting":"Hello!"}

Function 任何函数。函数不需要是类成员 lambda x: x > 0
def isPositive(x): return x > 0
def addem(x,y,z):
  return x + y + z
Class 任意类。类是类特性和函数(称为方法)的名称空间 class MyClass:
  x = 1
  y = 2
class subclass(superClass):
  def method1(self): ...
  def method2(self, arg1, arg2): ...
Module 一个包含变量、函数和/或类定义的源文件(和名称空间) 任何定义了可导入变量、函数和/或者类的 .py 文件。如果它没有定义任何这些内容,那么它就称为脚本

  语句类型

  Jython 提供了表 2 中总结的语句类型。

  表 2. Jython 语句类型总结

语句 注释 示例 Expression 任何表达式。抛弃它的结果。常使用它的副面作用 (1 * 2) ** 3 / 4
"string"[2:] == "ring"
someFunction(1, 2, 3)
Assignment 将一个表达式赋值给目标 x = 3
x = f(1,32,3) * 10 + x
list[1:3] = [7, 8, 9 , 10]
Augmented Assignment 用表达式更新目标 x *= 100
s += "…"
Unpacking Assignment 将一组元素指派给多个目标;对于访问元组成员(tuple member)和列表成员非常方便。注意表达式 1,2,3 是 (1,2,3) 的缩写形式 x,y,z = 1,2,3 Multiple Assignment 向多个目标指派同一表达式 z = y = z = 10 Pass 无操作 pass If

 

  If/Else

  If/Elif/Else

条件处理 if x < 0: x = -x
if x == 3:
  print "It's three!"
if  x == 1: ...
elif x == 2: ...
elif x == 3: ...
else: print "Bad value " + x
While 针对某一条件进行循环 x = 10
while x > 0:
  print x  
  x -= 1
For 迭代一个序列。使用 range 来迭代数字序列 for i in range(10): print i
for c in "A string":
  print c
for i in (10, "Hello", 1e6):
  print func(i)
Continue 进入下一个循环(while/for)迭代 for i in range(10):
  if not testOk(i):
    continue
  :
Break 退出循环(while/for) for i in range(10):
  if testBad(i):
    break
  :
Delete 删除一个变量或一组元素或类特性 del x
del list[3:5]
del x.attr1
Global 声明对全局值的引用;用在函数中 x,y,z = 0,1,2
def f(a,b,c):
  global x,y,z
  :
Print 把达式输出到一个流中 print "Hello World!"
print "Hello","World!"
print "Hello" + ' ' + "World!"
msg = "Name: %(last)s, (first)s"
data = {'last':"Feigenbaum", 'first':"Barry"}
print >>out, msg % data
Assert 断言一个条件为真 def process(v):
  assert v > 0, "Illegal value %i" % v
  :
Import 导入模块的全部或部分 import sys
from sys import argv
from javax import swing
from java.util import Map
Execute 将一个字符串/文件作为子程序执行。还有一个相关的函数 exec ,它执行一个构造的字符串并返回其结果。这种支持使您可以动态地创建程序 globals = {'x':1, 'y':2, 'z':3}
locals = {'list':(1,2,3)}
code = "print x, y, z, list"
exec code in globals, locals
Try/Except 在一个异常处理程序中执行代码 try:
  x = int(string)
except ValueError, e:
  x = 0
Try/Finally 利用清理例程执行代码 f = open("test.dat")
try:
  lines = f.readlines()
finally:
  f.close()
Raise 创建并抛出一个异常 def factorial(x):
  raise ValueError, "x must be > 0"
  :
Define 定义一个函数;参数和/或关键字可能是可选的;函数是一般性的,因而可以进行非常灵活的编程 def f(x,y=2,z=10): return x+y+z
q = f(1,2) + f(3) + f(4,5,6)
s = f("a","b","c")
Return 从函数中返回,可以有返回值 return 10 Class 定义一个类(特性和方法的容器) class X: pass
class MyClass(SomeClass): 
  pass

  在代码中学习 Jython!

  到了现在,您应当对 Jython 的结构和语法有了更多的了解。剩下的内容最好是通过分析几个简单然而完整的工作程序进行学习。首先是经典的示例程序——Hello World!——它在 Jython 中如下所示:

  清单 1. Jython 的 Hello World!

print "Hello World!"

  在 Jython 中(就像在 Python 中),源文件使用扩展名“ py”。“Hello World”语句放在 hello.py 之类的源文件中时,就是一个完整的程序。

  作为比较,看一下等效的 Java 程序,看看下面的 hello.java 文件:

  清单 2. Java 的 Hello World!

public class hello {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

  Jython 的格式显然更简单,通常都是如此。Jython 好的方面当然是代码可以直接解释,也可以翻译成 Java 源代码并自动编译为类文件。可以用下面的 Java 命令来运行 Hello World 程序:

C:>java -Dpython.home=C:jython-2.1 -cp C:jython-2.1jython.jar
   org.python.util.jython hello.py

  也可以用下面更方便的 Jython 脚本运行这个程序:

C:>jython hello.py

  回显,回显, echo.py!

  然后,尝试一个增加了一些功能的示例,这是一个小程序,它允许您回显(echo)命令行参数。对于程序 echo,使用以下代码(这些代码在 echo.py 中):

  清单 3. 回显命令行参数的示例程序

import sys
count = 1
for arg in sys.argv[1:]:
  print 'Argument %i=%s' % (count, arg)
  count += 1

  同样,这个完整的 Jython 程序揭示了 Jython 的一些关键特性。要注意的第一件事是每一行就是一个语句——不需要分号(;)来结束这些行——并且变量没有声明。

  您还应当注意命令行参数是通过内置符号 sys.argv 访问的,它是一系列字符串。 sys 是一个标准模块,它包含有用的值和函数,还有其他许多标准模块可供使用。第一个 argv 元素( 0 )是程序名(即 echo.py)。要跳过它,取走 argv 列表的 片段, 实际开始位置为一(1),然后迭代直到列表的最后。

  循环是通过 for 语句组实现的。 for 语句正文是 for 之后缩进的那些行。 print 语句示范了使用 Jython 的字符串格式编排功能(类似于 C/C++ 的 printf 和 Java 1.5 的新的 print 功能)。

  还可以更精确地将上述代码写为以下形式:

from sys import argv
for i in range(1, len(argv)): print 'Argument %i=%s' % (i, argv[i])

  在这个示例中,使用 range 以索引 sys.argv 列表。因为 argv 变量是直接导入的,所以不需要限定。注意 argv 列表的长度是通过 len 函数而不是作为方法获得的,这是 Jython 的一种用法,在许多语言中也常见。还有许多其他函数可用。

  利用 factorial.py 进行阶乘!

  我们用高速缓存阶乘计算器 factorial.py 作为类定义的练习,如清单 4 所示。

  清单 4. 高速缓存阶乘计算器

class Factorial:
  ''' A factorial calculator '''
  seen = {} # cache of prior calculated values  
  def __init__ (self, value):
    self.__value = value
  def compute (self, value=None):    
    ''' calculate the result '''
    if value is None: value = self.__value  # default
    if  value < 0:     # bad arg!
      raise ValueError, 'arg < 0'
    elif value < 2:     # base case
      return 1L      # insure long integers are used
    else:          # need to calculate
      if not value in Factorial.seen.keys():  # not done before?
        # calculate this value and cache it
        Factorial.seen[value] = value *
           Factorial(value - 1).compute()
      return Factorial.seen[value] # get value from the cache

  这里可以看到 Jython 的许多新特性。首先,用明确的( # )标识引入注释。一个类或者方法可以有文档注释字符串(可以用使用用法类似于 JavaDoc 的工具进行处理)作为其第一行。与赋值不同,语句是用关键字引入的。类和方法正文没有包围在定界符中,而是由缩进的正文来体现。

  而且,类是用 class 语句声明的。 def 语句引入方法。类特性是用类中的赋值创建的,而实例特性是用构造函数方法 __init__ 中的赋值创建的。 seen 变量是一个字典。实例是将类对象作为函数调用而创建的。方法是用点(.)运算符调用的。

  您还会注意到 self 变量是方法的接收器(就像 Java 语言中的 this ),在方法中,所有对实例特性或者类的方法的引用都必须用 self 限定。类变量(像在 Java 语言中一样)是用类名限定的。

  函数可以有默认参数,如 compute 函数的值参数所示。如果没有给出参数值,就使用 __value 实例特性。

  现在,显然可以看出 Jython 支持 Java 语言的所有功能,但是有时使用了不同的语法。例如,Jython 的 raise 语句与 Java 语言的 throw 语句是相同的。

  测试 factorial.py

  可以用下面的代码测试这个 Factorial 类,可以在文件 factorial.py 中找到这些代码(请参阅 参考资料):

if __name__ == "__main__":
  from sys import argv
  if  len(argv) == 1: vals = range(10)
  elif len(argv) == 2: vals = range(int(argv[1]))
  elif len(argv) == 3: vals = range(int(argv[1]), int(argv[2]))
  else: print " Incorrect range"; vals = ()
  for i in vals:
    print "Factorial(%i)=%i" % (i, Factorial(i).compute())
  print "Cache:", Factorial.seen

  在 Jython 中,可以结合类定义和测试用例。上述 if __name__…… 测试使得只有当文件作为命令运行时才运行测试用例代码。还可以用另一个文件引入这个文件,以重复使用 Factorial 类但不必运行测试用例。测试用例包含几个简单的命令参数处理,然后是一个循环,它计算指定值(如果有的话)的阶乘。最后,打印出缓存的值。 jython factorial.py 5 10 命令生成以下输出:

Factorial(5)=120
Factorial(6)=720
Factorial(7)=5040
Factorial(8)=40320
Factorial(9)=362880
Cache: {9: 362880L, 8: 40320L, 7: 5040L, 6: 720L, 5: 120L, 4: 24L, 3: 6L, 2: 2L}

  您可能还注意到上述示例中可以用 Jython long( ###L )类型计算不定长度整数 。

  一个 GUI 示例

  我将用一个创建 GUI 的更复杂的程序结束对 Jython 的介绍。在分析这个简单的基于 AWT 的应用程序时,您会亲眼看到从 Java 代码调用 Jython 的能力以及访问 Java 平台所有特性的能力。

  首先是创建一个为 Java Panel 子类的 GUI,称为 Scribble ,如清单 5 所示。然后,将创建一个 Frame 以显示这个 Panel 。

  清单 5. 类 Scribble 是 Java Panel 的子类

from java.awt import BorderLayout as BL, Color, Button, Panel
class Scribble(Panel):
  """ A simple GUI example """
  def __init__ (self): # constructor
    Panel.__init__(self, BL())
    self.add(Button('Clear', actionPerformed=self.doClear),
             BL.SOUTH)
    self.mouseDragged = self.doDrag
    self.mousePressed = self.doPress
    self.__last = 0, 0
  def doClear (self, event):
    """ clear background """
    g = self.graphics
    g.color = self.background 
    g.fillRect(0, 0, self.size.width, self.size.height)
  def doDrag (self, event):
    """ draw line from last to here """
    g = self.graphics
    g.color = Color.black
    lx, ly = self.__last
    x = event.x; y = event.y
    g.drawLine(lx, ly, x, y)  # draw new line segment
    self.__last = x, y  # save coordinates
  def doPress (self, event):
    """ save click point """
    self.__last = event.x, event.y
if __name__ == "__main__":
  def doClose (event):
    import sys
    sys.exit()
  from java.awt import Frame
  frame = Frame("Scribble", windowClosing=doClose)
  frame.add( Scribble() )
  frame.size = 400, 300
  frame.visible = 1

  在清单 5 中的代码中会注意到的第一件事是多条语句放到了同一行中,用分号作为分隔符。行可以在逗号之后继续。 import 语句可以导入多个类并可以重命名它们。 import 语句是可执行的,因此它可以放在需要的地方,同时避免导入不必要的符号。注意嵌套的 doClose 函数。还要注意在创建类实例时没有声明、也没有 new 运算符。

  self.__last 实例特性(它是一个私有特性,因为它以双下划线开头)包含上一次鼠标单击位置。每次发生鼠标拖动事件时,在保存的点和该事件位置之间就会画出一条黑线(可能很短)。

  还要注意事件处理程序是如何定义的。没有使用 Listener 接口,而是使用该类定义的匹配要注意的事件处理程序入口点的特性(例如 actionPerformed )设置给 Jython 函数。其他未使用的入口点(如果有的话)自动映射到空函数,因此这里不需要使用 Adapter 类。还要注意 Java 对象的所有 JavaBeans 属性(如针对 Graphics 对象 g )都可以作为 Jython 属性访问,而不是通过 get/set 方法。这极大地简化了编码,虽然愿意的话,仍然可以使用 get/set 方法。

  图 1 显示了正在使用的 Scribble GUI。

  图 1. 样例 Scribble GUI

alt.lang.jre: 了解 Jython

  结束语

  Propylon 的 CTO 及多部书的作者 Sean McGrath 说过 Jython 是没有大的厂商支持的、“最出色的 Java 生产率工具”,还称它为“Java 平台在 21 世纪生存的最有力武器”。在 alt.lang.jre 系列的第一期中,您自己已经看到了 Jython 给 Java 平台上的开发工作带来了易用性和高效性。我还简单地讨论了多语言支持对 Java 平台未来的重要性。

  下个月介绍 Groovy,您可以借此继续探索 JRE 的替代语言,这是一种新的语言,它结合了 Python、Ruby 和 Smalltalk 的最佳方面,而成为一种高效的、容易使用的和有趣的 Java 平台上的脚本语言。

  当然,关于 Jython 还有很多要说的内容,但是这需要一本书的篇幅。