[ Team LiB ] |
28.4 Jython: The Felicitous Union of Python and JavaJython is a version of Python written entirely in Java. Jython is very exciting for both the Python community and the Java community. Python users are happy that their current Python knowledge can be applied in Java-based projects; 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 by working in a powerful interactive environment. Jython is available from http://www.jython.org, with license and distribution terms similar to those of CPython (which is what the reference implementation of Python is called when contrasted with Jython). The Jython installation includes several parts:
Using Jython is very similar to using Python: ~/book> jython Jython 2.1 on java1.3.1_03 (JIT: null) Type "copyright", "credits" or "license" for more information. >>> 2 + 3 5 In fact, Jython works almost identically to CPython. For an up-to-date listing of the differences between the two, see http://www.jython.org/docs/differences.html. The most important differences are:
28.4.1 Jython Gives Python Programmers Access to Java LibrariesThe most important difference between Jython and CPython, however, is that Jython offers the Python programmer seamless access to Java libraries. Consider the following program, jythondemo.py, the output of which is shown in Figure 28-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 example!', actionPerformed=exit) frame.contentPane.add(button) frame.pack( ) Figure 28-5. The output of jythondemo.pyThis simple program demonstrates how easy it is to write a Python program that uses the Swing Java GUI framework.[4] The first line imports the swing Java package (the pawt module figures out the exact location of Swing in your particular Java installation, which can be in java.awt.swing, in com.sun.java.swing, or maybe in javax.swing—it also includes some utility code such as the GridBag class, which we use in the grapher.py program below). 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 jythondemo.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, Jython performs a lot of work behind the scenes. For example, when Jython 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. Jython also performs on-the-fly conversion between Python types and Java types. In jythondemo.py, for example, the text of the button ('This is a Swinging example!') is a Python string. Before the constructor for JButton is called, Jython 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. Jython makes it unnecessary to create an ActionListener class with a single actionPerformed method, although you can use the more verbose form if you wish. 28.4.2 Jython as a Java Scripting LanguageJython 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 Jython distribution. 28.4.3 A Real Jython/Swing Application: grapher.pyThe grapher.py program (output shown in Figure 28-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. Figure 28-6. Output of grapher.pyThe 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, os default_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 Jython installed, play around with other possibilities for both the Setup and Expression text widgets. The key asset of using Jython 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 Python code in these text widgets! Jython is still very much a work in progress; the Jython developers are constantly refining the interface between Python and Java, optimizing it, and keeping up with Python upgrades. Jython, 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. |
[ Team LiB ] |