JPython is a recently released version of Python written entirely in Java by Jim Hugunin. JPython is a very exciting development for both the Python community and the Java community. Python users are happy that their current Python knowledge can transfer to Java-based development environments; Java programmers are happy that they can use the Python scripting language as a way to control their Java systems, test libraries, and learn about Java libraries from an interpreted environment.
JPython is available from http://www.python.org/jpython, with license and distribution terms similar to those of CPython (which is what the reference implementation of Python is called when contrasted with JPython).
The JPython installation includes several parts:
jpython
: The equivalent of the Python program
used throughout the book.
jpythonc
: Takes a JPython program and compiles it
to Java class files. The resulting Java class files can be used as
any Java class file can, for example as applets, as servlets, or as
beans.
A set of modules that provide the JPython user with the vast majority of the modules in the standard Python library.
A few programs demonstrating various aspects of JPython programming.
Using JPython is very similar to using Python:
~/book> jpython
JPython 1.0.3 on java1.2beta4
Copyright 1997-1998 Corporation for National Research Initiatives
>>> 2 + 3
5
In fact, JPython works almost identically to CPython. For an up-to-date listing of the differences between the two, see http://www.python.org/jpython/differences.html. The most important differences are:
JPython is currently slower than CPython. How much slower depends on the test code used and on the Java Virtual Machine JPython is using. JPython’s author has, on the other hand, explored very promising optimizations, which could make future versions of JPython as fast or faster than CPython.
Some of the built-ins or library modules aren’t available for
JPython. For example, the os.system()
call is not
implemented yet, as doing so is difficult given Java’s
interaction with the underlying operating system. Also, some of the
largest extension modules such as the Tkinter GUI framework
aren’t available, because the underlying tools (the Tk/Tcl
toolkit, in the case of Tkinter) aren’t available in Java.
The most important difference between JPython and CPython, however, is that JPython offers the Python programmer seamless access to Java libraries. Consider the following program, jpythondemo.py, the output of which is shown in Figure 10.5.
from pawt import swing import java def exit(e): java.lang.System.exit(0) frame = swing.JFrame('Swing Example', visible=1) button = swing.JButton('This is a Swinging button!', actionPerformed=exit) frame.contentPane.add(button) frame.pack()
This simple program demonstrates how easy it is to write a Python
program that uses the Swing Java GUI framework.[73] The first line imports the
swing
Java package (the pawt
module figures out the exact location of Swing, which can be in
java.awt.swing
, in
com.sun.java.swing
, or maybe in
javax.swing
). The second line imports the
java
package that we need for the
java.lang.System.exit()
call. The fourth line
creates a JFrame
, setting its bean property
visible
to true. The fifth line creates a
JButton
with a label and specifies what function
should be called when the button is clicked. Finally, the last two
lines put the JButton
in the
JFrame
and make them both visible.
Experienced Java programmers might be a bit surprised at some of the
code in jpythondemo.py
, as it has some
differences from the equivalent Java program. In order to make using
Java libraries as easy as possible for Python users, JPython performs
a lot of work behind the scenes. For example, when JPython imports a
Java package, it actively tracks down the appropriate package, and
then, using the Java Reflection API, finds the contents of packages,
and the signatures of classes and methods. JPython also performs
on-the-fly conversion between Python types and Java types. In
jpythondemo.py
, for example, the text of the
button ('This is a Swinging example!'
) is a Python
string. Before the constructor for JButton
is
called, JPython finds which variant of the constructor can be used
(e.g., by rejecting the version that accepts an
Icon
as a first argument), and automatically
converts the Python string object to a Java string object. More
sophisticated mechanisms allow the convenient
actionPerformed=exit
keyword argument to the
JButton
constructor. This idiom isn’t
possible in Java, since Java can’t manipulate functions (or
methods) as first-class objects. JPython makes it unnecessary to
create an ActionListener
class with a single
actionPerformed
method, although you can use the
more verbose form if you wish.
JPython is gaining in popularity because it allows programmers to explore the myriad Java libraries that are becoming available in an interactive, rapid turnaround environment. It also is proving useful to embed Python as a scripting language in Java frameworks, for customization, testing, and other programming tasks by end users (as opposed to systems developers). For an example of a Python interpreter embedded in a Java program, see the program in the demo/embed directory of the JPython distribution.
The grapher.py
program (output shown in Figure 10.6) allows users to graphically explore the
behavior of mathematical functions. It’s also based on the
Swing GUI toolkit. There are two text-entry widgets in which Python
code should be entered. The first is an arbitrary Python program
that’s invoked before the function is drawn; it imports the
needed modules and defines any functions that might be needed in
computing the value of the function. The second text area (labeled
Expression
:) should be a Python expression (as in
sin(x)
), not a statement. It’s called for
each data point, with the value of the variable x
set to the horizontal coordinate.
The user can control whether to draw a line graph or a filled graph,
the number of points to plot, and what color to plot the graph in.
Finally, the user can save configurations to disk and reload them
later (using the pickle
module) Here is the
grapher.py program:
from pawt import swing, awt, colors, GridBag RIGHT = swing.JLabel.RIGHT APPROVE_OPTION = swing.JFileChooser.APPROVE_OPTION import java.io import pickle, osdefault_setup = """from math import *
def squarewave(x,order):
total = 0.0
for i in range(1, order*2+1, 2):
total = total + sin(x*i/10.0)/(float(i))
return total
"""
default_expression = "squarewave(x, order=3)" class Chart(awt.Canvas): color = colors.darkturquoise style = 'Filled' def getPreferredSize(self): return awt.Dimension(600,300) def paint(self, graphics): clip = self.bounds graphics.color = colors.white graphics.fillRect(0, 0, clip.width, clip.height) width = int(clip.width * .8) height = int(clip.height * .8) x_offset = int(clip.width * .1) y_offset = clip.height - int(clip.height * .1) N = len(self.data); xs = [0]*N; ys = [0]*N xmin, xmax = 0, N-1 ymax = max(self.data) ymin = min(self.data) zero_y = y_offset - int(-ymin/(ymax-ymin)*height) zero_x = x_offset + int(-xmin/(xmax-xmin)*width) for i in range(N): xs[i] = int(float(i)*width/N) + x_offset ys[i] = y_offset - int((self.data[i]-ymin)/(ymax-ymin)*height) graphics.color = self.color if self.style == "Line": graphics.drawPolyline(xs, ys, len(xs)) else: xs.insert(0, xs[0]); ys.insert(0, zero_y) xs.append(xs[-1]); ys.append(zero_y) graphics.fillPolygon(xs, ys, len(xs)) # draw axes graphics.color = colors.black graphics.drawLine(x_offset,zero_y, x_offset+width, zero_y) graphics.drawLine(zero_x, y_offset, zero_x, y_offset-height) # draw labels leading = graphics.font.size graphics.drawString("%.3f" % xmin, x_offset, zero_y+leading) graphics.drawString("%.3f" % xmax, x_offset+width, zero_y+leading) graphics.drawString("%.3f" % ymin, zero_x-50, y_offset) graphics.drawString("%.3f" % ymax, zero_x-50, y_offset-height+leading) class GUI: def __init__(self): self.numelements = 100 self.frame = swing.JFrame(windowClosing=self.do_quit) # build menu bar menubar = swing.JMenuBar() file = swing.JMenu("File") file.add(swing.JMenuItem("Load", actionPerformed = self.do_load)) file.add(swing.JMenuItem("Save", actionPerformed = self.do_save)) file.add(swing.JMenuItem("Quit", actionPerformed = self.do_quit)) menubar.add(file) self.frame.JMenuBar = menubar # create widgets self.chart = Chart(visible=1)self.execentry = swing.JTextArea(default_setup, 8, 60)
self.evalentry = swing.JTextField(default_expression,
actionPerformed = self.update)
# create options panel optionsPanel = swing.JPanel(awt.FlowLayout( alignment=awt.FlowLayout.LEFT)) # whether the plot is a line graph or a filled graph self.filled = swing.JRadioButton("Filled", actionPerformed=self.set_filled) optionsPanel.add(self.filled) self.line = swing.JRadioButton("Line", actionPerformed=self.set_line) optionsPanel.add(self.line) styleGroup = swing.ButtonGroup() styleGroup.add(self.filled) styleGroup.add(self.line) # color selection optionsPanel.add(swing.JLabel("Color:", RIGHT)) colorlist = filter(lambda x: x[0] != '_', dir(colors)) self.colorname = swing.JComboBox(colorlist) self.colorname.itemStateChanged = self.set_color optionsPanel.add(self.colorname) # number of points optionsPanel.add(swing.JLabel("Number of Points:", RIGHT)) self.sizes = [50, 100, 200, 500] self.numpoints = swing.JComboBox(self.sizes) self.numpoints.selectedIndex = self.sizes.index(self.numelements) self.numpoints.itemStateChanged = self.set_numpoints optionsPanel.add(self.numpoints) # do the rest of the layout in a GridBag self.do_layout(optionsPanel) def do_layout(self, optionsPanel): bag = GridBag(self.frame.contentPane, fill='BOTH', weightx=1.0, weighty=1.0) bag.add(swing.JLabel("Setup Code: ", RIGHT)) bag.addRow(swing.JScrollPane(self.execentry), weighty=10.0) bag.add(swing.JLabel("Expression: ", RIGHT)) bag.addRow(self.evalentry, weighty=2.0) bag.add(swing.JLabel("Output: ", RIGHT)) bag.addRow(self.chart, weighty=20.0) bag.add(swing.JLabel("Options: ", RIGHT)) bag.addRow(optionsPanel, weighty=2.0) self.update(None) self.frame.visible = 1 self.frame.size = self.frame.getPreferredSize() self.chooser = swing.JFileChooser() self.chooser.currentDirectory = java.io.File(os.getcwd()) def do_save(self, event=None): self.chooser.rescanCurrentDirectory() returnVal = self.chooser.showSaveDialog(self.frame) if returnVal == APPROVE_OPTION: object = (self.execentry.text, self.evalentry.text, self.chart.style, self.chart.color.RGB, self.colorname.selectedIndex, self.numelements) file = open(os.path.join(self.chooser.currentDirectory.path, self.chooser.selectedFile.name), 'w') pickle.dump(object, file) file.close() def do_load(self, event=None): self.chooser.rescanCurrentDirectory() returnVal = self.chooser.showOpenDialog(self.frame) if returnVal == APPROVE_OPTION: file = open(os.path.join(self.chooser.currentDirectory.path, self.chooser.selectedFile.name)) (setup, each, style, color, colorname, self.numelements) = pickle.load(file) file.close() self.chart.color = java.awt.Color(color) self.colorname.selectedIndex = colorname self.chart.style = style self.execentry.text = setup self.numpoints.selectedIndex = self.sizes.index(self.numelements) self.evalentry.text = each self.update(None) def do_quit(self, event=None): import sys sys.exit(0) def set_color(self, event): self.chart.color = getattr(colors, event.item) self.chart.repaint() def set_numpoints(self, event): self.numelements = event.item self.update(None) def set_filled(self, event): self.chart.style = 'Filled' self.chart.repaint() def set_line(self, event): self.chart.style = 'Line' self.chart.repaint() def update(self, event):context = {}
exec self.execentry.text in context
each = compile(self.evalentry.text, '<input>', 'eval')
numbers = [0]*self.numelements
for x in xrange(self.numelements):
context['x'] = float(x)
numbers[x] = eval(each, context)
self.chart.data = numbers if self.chart.style == 'Line': self.line.setSelected(1) else: self.filled.setSelected(1) self.chart.repaint() GUI()
The logic of this
program is fairly straightforward, and the class and method names
make it easy to follow the flow of control. Most of this program
could have been written in fairly analogous (but quite a bit longer)
Java code. The parts in bold, however, show the power of having
Python available: at the top of the module, default values for the
Setup
and Expression
text
widgets are defined. The former imports the functions in the
math
module and defines a function called
squarewave
. The latter specifies a call to this
function, with a specific order
parameter (as that
parameter grows, the resulting graph looks more and more like a
square wave, hence the name of the function). If you have Java,
Swing, and JPython installed, feel free to play around with other
possibilities for both the Setup
and
Expression
text widgets.
The key asset of using
JPython instead of Java in this example
is in the update
method: it simply calls the
standard Python exec
statement with the
Setup
code as an argument, and then calls
eval
with the compiled version of the
Expression
code for each coordinate. The user is
free to use any part of Python in these text widgets!
JPython is still very much a work in progress; Jim Hugunin is constantly refining the interface between Python and Java and optimizing it. JPython, by being the second implementation of Python, is also forcing Guido van Rossum to decide what aspects of Python are core to the language and what aspects are features of his implementation. Luckily, Jim and Guido seem to be getting along and agreeing on most points.
[73] Documentation for Swing and the Java Foundation Classes is available online at http://java.sun.com/products/jfc/index.html. Alternatively, Robert Eckstein, Marc Loy, and Dave Wood have published a thorough review of the Swing toolkit for Java, Java Swing, published by O’Reilly & Associates.