Консалтинг и автоматизация в области управления
эффективностью банковского бизнеса

Журнал ВРМ World

JPython: Удачный союз Python и Java.
(Фрагмент Гл. 10 "Среды и приложения" книги "Изучение Python")

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.
  • Несколько программ, демонстрирующих различные аспекты программирования на JPython.

Использование JPython во многом сходно с использованием Python:

~/book> jpython
JPython 1.0.3 on java1.2beta4
Copyright 1997-1998 Corporation for National Research Initiatives
>>> 2 + 3
5

На самом деле, JPython работает почти идентично CPython. Для своевременного отслеживания отличий между двумя реализациями см. http://www.python.org/jpython/differences.html. Важнейшими различиями являются следующие:
  • JPython пока еще медленнее, чем CPython. То, насколько он медленнее, во многом зависит от использованного тестового кода и от Виртуальной Машины Java, которую использует JPython. С другой стороны, автор JPython сделал ряд многообещающих оптимизаций, которые могут сделать будущие версии JPython такими же или даже более быстрыми, чем CPython.
  • Некоторые из встроенных или библиотечных модулей недоступны для JPython. Например, до сих пор не реализован вызов os.system(), поскольку сделать это достаточно трудно, учитывая взаимодействие Java с лежащей в ее основе операционной системой. Кроме того, некоторые из наиболее крупных модулей расширения (таких, как среда Tkinter GUI framework) отсутствуют, так как в Java не существует средств, лежащих в их основе (набора инструментов Tk/Tcl toolkit в случае Tkinter).

JPython предоставляет программистам Python доступ к библиотекам Java

Самое важное различие между JPython и CPython, тем не менее, состоит в том, что JPython предлагает программисту Python непосредственный доступ к библиотекам Java. Рассмотрим программу jpythondemo.py, результат работы которой представлен на Рис. 10-5.




Рис. 10-5. Результат работы jpythondemo.py

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

Растущая популярность JPython объясняется тем, что он позволяет программистам исследовать бесчисленное множество библиотек Java, которые становятся доступны в интерактивной среде. Кроме того, доказано, что очень удобно, когда Python встроен в качестве скриптового языка в среды Java, для настройки, тестирования и других программистских задач, выполняемых пользователями (в противоположность системным разработчикам). Чтобы увидеть пример интерпретатора Python, встроенного в Java-программу, см. программу в директории demo/embed дистрибутива JPython.

Реальное приложение JPython/Swing: grapher.py

Программа 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 или планируете в будущем -