JPython представляет собой последнюю вышедшую версию Python, написанную на Java Джимом Хьюдженином. Сообщества Python и Java очень заинтересованы разработкой JPython. Пользователи Python рады возможности перенести свои сегодняшние знания по языку Python в основанные на Java среды разработки, а программисты Java - что могут использовать скриптовый язык Python в своих Java-системах для тестирования библиотек и изучения Java-библиотек из интерпретируемой среды.
JPython можно получить на http://www.python.org/jpython с лицензией и условиями распространения, аналогичными имеющимся для CPython (который часто сравнивают с JPython).
JPyhon содержит несколько элементов:
jpython
, являющийся эквивалентом программы на Python,
использующийся на протяжении всей книги.jpythonc
, перекомпилирующий программу на JPython в файлы
классов Java. Полученные в результате файлы классов Java могут использоваться
как любые другие файлы классов Java, например, как апплеты, сервлеты или
beans.Использование JPython во многом сходно с использованием Python:
~/book> jpython
JPython 1.0.3 on java1.2beta4
Copyright 1997-1998 Corporation for National Research Initiatives
>>> 2 + 3
5
os.system()
, поскольку
сделать это достаточно трудно, учитывая взаимодействие Java с лежащей в ее
основе операционной системой. Кроме того, некоторые из наиболее крупных модулей
расширения (таких, как среда Tkinter GUI framework) отсутствуют, так как в Java
не существует средств, лежащих в их основе (набора инструментов Tk/Tcl toolkit
в случае Tkinter).Самое важное различие между JPython и CPython, тем не менее, состоит в том, что JPython предлагает программисту Python непосредственный доступ к библиотекам Java. Рассмотрим программу jpythondemo.py, результат работы которой представлен на Рис. 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()
Эта простая программа демонстрирует, насколько просто создавать программы на
Python, использующие среду
Swing Java GUI
framework. Первая строка импортирует Java-пакет swing
(модуль
pawt
вычисляет точное местонахождение Swing, который может
находиться в java.awt.swing
, в com.sun.java.swing
или, возможно, в javax.swing
). Вторая строка импортирует пакет
java
, необходимый нам для вызова
java.lang.System.exit()
. Четвертая строка создает рамку
JFrame
, присваивая ее свойству visible
значение
"истина". Пятая строка создает кнопку JButton
с меткой и
определяет, какая функция должна вызываться при нажатии этой кнопки. И наконец,
последние две строки помещают кнопку JButton
в рамку
JFrame
и делают это все видимым.
Опытные программисты Java, возможно, будут несколько удивлены некоторыми
фрагментами кода в jpythondemo.py, поскольку они имеют ряд отличий от
эквивалентной им Java-программы. Для того чтобы упростить использование
библиотек Java пользователями Python, JPython выполняет большой объем работы
незаметно для них. Например, когда JPython импортирует пакет Java, он активно
исследует соответствующий пакет, и затем, используя Java Reflection API,
находит содержимое пакетов и подписи классов и методов. Кроме того, JPython "на
лету" конвертирует между собой типы Python и Java. В jpythondemo.py,
например, текст кнопки ('This is a Swinging example!'
)
представляет собой строку Python. Перед вызовом конструктора для
JButton
, JPython находит наиболее подходящий конструктор
(например, отклоняя версию, принимающую Icon в качестве первого аргумента), и
автоматически конвертирует строковый объект Python в строковый объект Java.
Более сложные механизмы позволяют конструктору JButton
принимать
ключевой параметр actionPerformed=exit
. Такой стиль невозможен в
Java, поскольку Java не имеет возможности манипулировать функциями (или
методами) как объектами первого класса. JPython исключает необходимость
создания класса ActionListener
с единственным методом
actionPerformed
, хотя при желании вы можете использовать более
многословную форму.
Растущая популярность JPython объясняется тем, что он позволяет программистам исследовать бесчисленное множество библиотек Java, которые становятся доступны в интерактивной среде. Кроме того, доказано, что очень удобно, когда Python встроен в качестве скриптового языка в среды Java, для настройки, тестирования и других программистских задач, выполняемых пользователями (в противоположность системным разработчикам). Чтобы увидеть пример интерпретатора Python, встроенного в Java-программу, см. программу в директории demo/embed дистрибутива JPython.
Программа grapher.py (результат работы которой показан на Рис. 10-6)
предоставляет пользователям возможность графически исследовать поведение
математических функций. Она также создана на основе набора инструментов Swing
GUI toolkit. В ней есть два окошка для ввода текста, в который должен вводиться
код на языке Python. Первый текст представляет собой произвольную программу на
языке Python, вызваемую перед тем, как отображать функцию; он импортирует
необходимые модули и определяет любые методы, которые могут пригодиться при
вычислении значений функции. Вторая текстовая область (помеченная как
Expression:
) должна содержать выражение (но не утверждение) на
языке Python (как в sin(x)
). Оно вызывается для каждой точки
данных, с параметром, равным координате по оси x
.
Рис. 10-6. Результат работы grapher.py
Пользователь может контролировать стиль графика (линией или с заполнением),
дискретность и цвет. И, наконец, пользователь может сохранять конфигурацию на
диск и снова загружать ее (с помощью модуля pickle
). Вот код
программы grapher.py:
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()
Логика этой программы очень проста, и имена классов и методов облегчают ее
понимание. Большая часть могла бы быть написана на практически аналогичном
(только чуть более длинном) коде Java. Кстати, здесь виден выигрыш от
использования языка Python: вверху модуля определены значения по умолчанию для
элементов управления Setup
и Expression
. Первый из
них импортирует функции из модуля math и определяет функцию с именем
squarewave
. Второй описывает вызов этой функции со специфическим
параметром order
(с ростом этого параметра получающийся в
результате график все больше и больше напоминает меандр, отсюда и имя функции).
Если у вас установлены Java, Swing и JPython, вы можете экспериментировать и с
другими возможностями Setup
и Expression
.
Важнейшее преимущество использования JPython вместо Java в данном примере
состоит в методе update
: он просто вызывает утверждение Python
exec
с кодом Setup
в качестве аргумента, и затем
вызывает eval
с уже откомпилированным кодом
Expression
для каждой координаты. Пользователи могут свободно
вставлять любую часть кода Python в эти текстовые окна!
JPython все еще в значительной степени находится в процессе разработки; Джим Хьюдженин постоянно совершенствует и оптимизирует интерфейс между Python и Java. Кроме того, JPython, будучи второй реализацией Python, стимулировал Гвидо ван Россума отделить ключевые для языка аспекты Python от тех, что представляют собой просто особенности его реализации. К счастью, Джим и Гвидо, похоже, работают сообща и достигают согласия по большей части вопросов.
Данная статья была написана в апреле прошлого года. С тех пор JPython сделал несколько больших шагов вперед. Проект сейчас носит название Jython и 31 декабря вышел релиз 2.0, который синхронизирован с версией CPython 2.0. В него добавлены списочные встраивания, расширенные операторы вызова, печати и присваивания. Он также включает модули регулярных выражений Apache Jakarta ORO regular expression modules. Если Вы работаете с Java или планируете в будущем -