Умножение языков (Multiple languages)


Эта глава оценивает значение пересечения языковых границ. Часто бывает очень выгодно использовать для решения проблемы не один язык программирования, вместо того, чтобы стопориться, используя единый язык. Как вы увидите в этой главе, проблема, которая имеет очень трудное или утомительное решение в одном языке, часто может быть решена легко и просто в другом. Если вы можете комбинировать использование языков, вы можете создавать ваш продукт быстрее и дешевле.

Наиболее явно эта идея воплощена в шаблоне проектирования Интерпретатор, который добавляет интерпретируемый язык в вашу программу, что позволяет конечному пользователю легко подстраивать решение. В Java наиболее легким и мощным средством сделать это является Jython [9], реализация языка Python в чистом Java байт-коде.

Интерпретатор решает определенные проблемы - а именно, создание скриптового языка для пользователя. Но иногда проще и быстрее временно переключится на другой язык для решения определенного аспекта вашей проблемы. Вы не создаете интерпретатор, вы просто пишете некоторый код на другом языке. Опять таки, Jython является отличным примером, и CORBA позволяет вам пересекать языковые границы.

Мотивация интерпретатора (Interpreter motivation)

Если пользователь приложения нуждается в большей гибкости во время выполнения, например, для создания скриптов, описывающих поведение системы, вы можете использовать шаблон проектирования Интерпретатор (Interpreter) . В этом случае вы создаете и встраиваете интерпретатор языка в вашу программу.

Помните, что каждый шаблон проектирования позволяет изменять один или несколько факторов, так что очень важно сначала осознать, какой фактор изменяется. Иногда конечный пользователь вашего приложения (а не программист приложения) нуждается в полной гибкости подхода, которым он конфигурирует некоторый аспект программы. Таким образом, ему нужно, некоторого рода, простое программирование. Шаблон интерпретатора предоставляет такую гибкость, добавляя интерпретатор языка.

Проблема состоит в том, что разработка вашего собственного языка и построение интерпретатора отнимает много времени от процесса разработки приложения. Вы должны ответить, хотите ли вы закончить написание вашего приложения или создать новый язык программирования. Лучшим подходом является повторное использование кода: встраивание интерпретатора, который уже был построен и отлажен до вас. Язык программирования Python может быть свободно встроен в ваше прибыльное приложение без покупки каких-либо лицензионных соглашений, оплаты авторских гонораров или сделок любого другого рода.

Python является языком, который очень легко изучается, очень логичен при написании и чтении, поддерживает функции и объекты, имеет большой набор поддерживаемых библиотек и работает, фактически, на любой платформе. Вы можете загрузить Python и узнать больше о нем по ссылке www.Python.org.

Для решения Java проблем мы можем взглянуть на специальную версию Python, которая называется Jython. Он сгенерирован полностью в Java байт-коде, так что встраивание его в ваше приложение достаточно просто, и является переносимым, как и Java. Он имеет чрезвычайно прозрачный интерфейс с Java: Java может вызывать классы Python, а Python может вызывать классы Java.

Python разрабатывался с классами от самого начала, так что это реально чистый объектно-0риентированный язык (в то время как C++ и Java нарушают чистоту различными способами). Python масштабируется, так что вы можете создавать очень большие программы без потери контроля над кодом.

Для установки Python, зайдите на www.Python.org и следуйте ссылкам и инструкциям. Для установки Jython перейдите на http://jython.sourceforge.net. Скачайте этот .class файл, который является запускаемым в качестве установщика, когда вы выполните его с помощью Java. Вам также необходимо добавить jython.jar в вашу переменную CLASSPATH. Вы можете найти дополнительную инструкцию по установке на http://www.bruceeckel.com/TIPatterns/Building-Code.html.

Обзор Python

Для того, чтобы начать, здесь приведена краткая инструкция для опытных программистов (которыми вы должны быть, если читаете эту книгу). Вы можете обратиться к полной документации на www.Python.org (особенно невероятно полезна HTML страница "A Python Quick Reference"), а так же к ряду книг, таких как Learning Python Марка Лутца (Mark Lutz) и Дэвида Ашера (David Ascher) (O'Reilly, 1999).

Python часто называют скриптовым языком, но назначение скриптовых языков ограничено, особенно в рамках проблемы, которые они решают. Python, с одной стороны, является языком программирования, который поддерживает скрипты. Он является изумительным языком для скриптов, и вы можете обнаружить, что вы заменяете все свои bat-файлы, скрипты шелла и простенькие программы на Python-скрипты. Но на самом деле это намного больше, чем язык скриптов.

Python предназначен для того, чтобы быть очень чистым для написания и особенно для чтения. Вы можете найти, что он очень прост для чтения вашего собственного кода через долгое время после того, как вы его написали, а так же для чтения кода, написанного другими людьми. Это достигнуто частично через чистый, по сути синтаксический, но главный фактор в читаемости кода - это отступ - контекст в Python определяется отступом. Например:

##interpreter:if.py
response =
"yes"
if response == "yes":
  print
"affirmative"
 
val = 1
print "continuing..."
##~

'#' обозначает комментарий, который идет до конца строки, аналогично сочетанию '//' в C++ и Java.

Прежде всего заметьте, что основной синтаксис Python схож с C-шным, что видно из оператора if. Но в C if требует использование круглых скобок вокруг выражения, а в Python они не обязательны (но их использование не будет ошибкой).

Проверочное условие завершается двоеточием, которое указывает, что далее следует группа выражений с отступом, которая является "then" частью выражения if. В данном случает это выражение "print", которое посылает результат в стандартный вывод, и следующее присвоение переменной с именем val. Последующие выражения не имеют отступа, так что это больше не часть выражения if. Отступ может формироваться на любом уровне, точно так же как и фигурные скобки в C++ или Java, но в отличие от этих языков, нет правил (и нет аргументов) относительно того, где располагать фигурные скобки - компилятор заставляет, чтобы любой код был отформатирован аналогичным образом, что является одной из главных причин, по которой код Python хорошо читается.

Обычно Python имеет только одно выражение в строке (вы можете поместить больше, разделив их точкой с запятой), таким образом, завершающая точка с запятой не обязательны. Даже из короткого, приведенного выше, примера вы можете видеть, что язык разработан настолько простым, насколько это возможно, и к тому же еще очень легко читаемый.

Встроенные контейнеры

В таких языках, как C++ и Java контейнеры являются дополнительными библиотеками, а не интегрированными в язык. В Python неотъемлемой частью контейнеров для программирования является то, что они встроены в ядро языка: и списки, и ассоциированные массивы (а именно: карты, словари, хэш-таблицы) являются фундаментальными типами данных. Это прибавляет элегантности языку.

Кроме того, выражение for автоматически проходит по списку, а не просто перебором непрерывной последовательности чисел. Это приобретет больший смысл, когда вы подумаете об этом, так как вы почти всегда используете цикл for для прохода по массиву или по контейнеру. Python автоматически формализовал это, сделав for, использующим итератор, который работает с последовательностью. Вот пример:

## interpreter:list.py
list =
[ 1, 3, 5, 7, 9, 11 ]
print list
list.append
(13)
for x in list:
  print x
##~

Первая строка создает список. Вы можете напечатать список и он будет выглядеть точно так, как вы в него помещали (для сравнения, вспомните, что я создавал специальный класс Arrays2 в Thinking in Java, 2nd Edition, чтобы распечатывать массивы в Java). Список похож на Java контейнер - вы можете добавить новый элемент в него (в данном случае используется append( )) и список автоматически увеличит свой размер. В выражении for создается итератор x, который принимает каждое значение из списка.

Вы можете создать список чисел в помощью функции range( ), так что если вам нужно имитировать C-шный for, вы можете это сделать.

Заметьте, что в языке нет деклараций любого типа - просто появляется имя объекта, а Python подразумевает его тип по способу его использования. Таким образом, Python разработан так, что вам нужно просто жать кнопочки, которые должны. Вы обнаружите, после того, как поработаете с Python короткое время, что вы используете огромное число циклов, разделенных точкой с запятой, фигурными скобками и всеми сортами других дополнительных ненужных символов, которые требуются вашим языком программирования, отличным от Python, но на самом деле не описывающих то, для чего ваша программа предназначена.

Функции

Для создания функции в Python вы используете ключевое слово def, за которым следует имя функции и список аргументов, а за двоеточием начинается тело функции. Вот первый пример, включающий функцию:

## interpreter:myFunction.py
def myFunction
(response):
  val =
0
 
if response == "yes":
    print
"affirmative"
   
val = 1
 
print "continuing..."
 
return val

print myFunction
("no")
print myFunction("yes")
##~

Обратите внимание, что в сигнатуре функции нет информации о типе - все, что указано, это имя функции и идентификатор аргумента, но нет типа аргумента или типа возвращаемого значения. Python слабо типизированный язык программирования, что означает, что ему необходим минимум из возможно требуемых типов. Например, вы можете передать и вернуть различные типы для одной и той же функции:

## interpreter:differentReturns.py
def differentReturns
(arg):
 
if arg == 1:
   
return "one"
 
if arg == "one":
   
return 1

print differentReturns(1)
print differentReturns("one")
##~

Единственное ограничение состоит в том, что объект, который передается в функцию, был таким, чтобы функция могла работать с этим объектом, но с другой стороны, это не обязательно. Вот одна и та же функция применяет оператор '+' для целых чисел и для строк:

## interpreter:sum.py
def sum
(arg1, arg2):
 
return arg1 + arg2

print sum
(42, 47)
print sum('spam', "eggs")
##~

Когда оператор '+' используется со строками, это означает конкатенацию (да, Python поддерживает перегрузку операторов, и это прекрасная работа).

Строки

Приведенный выше пример также демонстрирует обработку строк в Python, которая является лучшей, по сравнению с любым языком, что я видел. Вы можете использовать одинарные или двойные кавычки для представления строк, что очень хорошо, потому, что если вы используете двойные кавычки, вы можете внутри использовать одинарные кавычки, и наоборот:

## interpreter:strings.py
print
"That isn't a horse"
print 'You are not a "Viking"'
print """You're just pounding two
coconut halves together."""
print '''"Oh no!" He exclaimed.
"It's the blemange!"'''
print r'c:pythonlibutils'
##~

Заметим, что Python был назван не в честь змеи, а скорее комедийной труппы Монти Пайтона (Monty Python), так что примеры фактически обязаны включать ссылки Python-esque.

Любой синтаксис с тройными кавычками включает новую строку. Это обычно полезно для таких задач, как генерация web страниц (Python особенно удобен в качестве CGI языка), так как вы можете просто включить тройные кавычки без каких-либо дополнений.

Литер 'r' справа перед строкой означает 'raw', это означает, что обратные слешы берутся как литеры и вам не нужно дополнительно печатать обратные слеши.

Подстановка в стоках исключительно легка, так как Python использует C-шный синтаксис подстановки printf( ), но только для всех строк. Вы просто следом за литерой '%' пишите значение для подстановки:

#[BT_286]#Substitution in strings is exceptionally easy, since Python uses C’s printf( ) substitution syntax, but for any string at all. You simply follow the string with a ‘%’ and the values to substitute:

## interpreter:stringFormatting.py
val =
47
print "The number is %d" % val
val2 =
63.4
s = "val: %d, val2: %f" % (val, val2)
print s
##~

Как вы видите, во втором случае, если вы имеете более одного аргументы, вы окружаете их круглыми скобками (здесь формеруется запись, которая является списком и не может быть изменена).

Поддерживается любое форматирование из printf( ), включая управление числом десятичных знаков и выравниванием. Python также поддерживает очень изощренные регулярные выражения.

Классы

Как и все остальное в Python, определение классов использует минимум дополнительного синтаксиса. Вы используете ключевое слово class, а внутри тела вы используете def для определения методов. Вот пример класса:

## interpreter:SimpleClass.py

class Simple:
  def __init__
(self, str):
    print
"Inside the Simple constructor"
   
self.s = str
  # Two methods:
  def show
(self):
    print self.s
  def showMsg
(self, msg):
    print msg +
':',
    self.show
() #

if __name__ == "__main__":
  # :
  x = Simple
("constructor argument")
 
x.show()
 
x.showMsg("A message")
##~

Оба метода имеют 'self' в качестве первого аргумента. C++ и Java прячут первый аргумент в методах класса, который является указателем на текущий объект, вызывающий метод, и может быть доступен при использовании ключевого слова this. Методы Python тоже используют ссылку на текущий объект, но когда вы определяете метод, вы должны явно указать ссылку в качестве первого аргумента. Традиционно эта ссылка называется self, но вы можете использовать любой идентификатор, который хотите (однако, если вы не используете self, вы, вероятно, запутаете большое число людей). Если вам нужна ссылка на поля объекта или на другой метод объекта, вы можете использовать self в выражении. Однако, когда вы вызываете метод для объекта, как в x.show( ), вам не обязательно указывать ссылку на объект - это делается за вас.

В тексте первый метод - специальный, как и любой идентификатор, который начинается с двойного символа подчеркивания. В этом случае он определяет конструктор, который вызывается автоматически при создании объекта, точно как в C++ и Java. Однако в нижней части примера вы можете видеть, что создание объекта выглядит как вызов функции с использованием имени класса. Синтаксис Python позволит вам понять, что на самом деле нет необходимости в ключевом слове new в C++ и Java.

Весь код нижней части является множеством выражения if, которое проверяет, если что-то, называемое __name__ равно __main__. Опять таки, двойное подчеркивание означает специальное имя. Объяснения для if состоит в том, что любой файл может быть использован как модуль библиотеки внутри другой программы (модули описаны коротко). В этом случает вам просто нужно определение класса, но вам не нужен код в нижней части файла, для выполнения. Это особое выражение if возвращает истину, когда вы запускаете этот файл напрямую. Это будет так, если вы напишите в командной строке:

Python SimpleClass.py

Однако если этот файл импортируется как модуль в другую программу, код __main__ не выполняется.

Сначала немного удивляет то, что вы определяете поля внутри методов, а не снаружи методов, как в C++ или Java (если вы создадите поля, используя стиль C++/Java, они неявно станут статическими полями). Для создания поля объекта вы просто объявляете его - используя self - внутри одного из методов (обычно в конструкторе, но не всегда), а место создается при вызове метода. Это выглядит немного странным для перешедших с C++ или Java, когда вы должны сначала решать, сколько места ваш объект будет занимать, не это привносит черезвычайную гибкость в программу.

Наследование

Поскольку Python слабо типизирован, то реально не приходится заботиться об интерфейсах - нужно только заботится о применимости операторов к объекту (фактически, ключевое слово Java interface исчезает в Python). Это означает, что наследование в Python отличается от наследования в C++ или Java, где вы часто наследуете просто для того, чтобы получить общий интерфейс. В Python единственная причина наследования является в наследовании реализации - для повторного использования кода базового класса.

Если вы наследуете от класса, вы должны сказать Pytho'у, чтобы он выделил этот класс в ваш новый файл. Python контролирует пространство имен так же агрессивно, как и Java, причем схожим образом (не смотря на склонность Python к упрощению). При каждом создании файла вы неявно создаете модуль (который аналогичен пакету в Java) с тем же именем, что и этот файл. Таким образом, в Python нет необходимости в ключевом слове package. Когда вы хотите использовать модуль, вы просто пишете import и указываете имя модуля. Python ищет PYTHONPATH точно так же, как Java ищет CLASSPATH (но по некоторым причинам Python не имеет тех же видов неувязок, которые делает Java) и читает файл. Для ссылки на любую функцию или класс внутри модуля вы даете имя модуля, а через точку имя функции или имя класса. Если вы не хотите проблем с именем квалификатора, вы можете сказать

from module import name(s)

Где "name(s)" может быть списком имен, разделенных запятыми.

Вы наследуете класс (или классы - Python поддерживает множественное наследование) перечисляя имя (имена) класса внутри круглых скобок после имени наследуемого класса. Обратите внимание, что класс Simple, который расположен в файле (и поэтому в модуле) с именем SimpleClass перенесен в новое пространство имен с помощью выражения import:

## interpreter:Simple2.py

from SimpleClass
import Simple

class Simple2(Simple):
  def __init__
(self, str):
    print
"Inside Simple2 constructor"
   
# Вы должны явно вызвать конструктор
    # базового класса:
    Simple.__init__
(self, str)
 
def display(self):
    self.showMsg
("Called from display()")
 
# Перегрузка метода базового класса
  def show
(self):
    print
"Overridden show() method"
   
# Вызов метода базового класса изнутри
    # перегруженного метода:
    Simple.show
(self)

class Different:
  def show
(self):
    print
"Not derived from Simple"

if __name__ == "__main__":
  x = Simple2
("Simple2 constructor argument")
 
x.display()
 
x.show()
 
x.showMsg("Inside main")
 
def f(obj): obj.show() # One-line определение
  f
(x)
 
f(Different())
##~

Simple2 наследуется от Simple и в конструкторе вызывается конструктор базового класса. В методе display( ) метод showMsg( ) может быть вызван, как метод self, но когда вызывается версия метода базового класса для перегружаемого вами метода, вы должны полностью квалифицировать имя и передать self в качестве первого аргумента, как показано в вызове конструктора базового класса. Это так же можно увидеть в перегруженной версии метода show( ).

В __main__ вы увидите (когда запустите программу), что конструктор базового класса был вызван. Вы также можете видеть, что метод showMsg( ) доступен в наследуемом классе, чего и следовало ожидать при наследовании.

Класс Different также имеет метод с именем show( ) , но этот класс не наследуется от Simple. Метод f( ), определенный в __main__, демонстрирует слабое типизирование: единственное о чем здесь проявлена забота, это чтобы метод show( ) был применим к obj, а на тип не накладывается каких-либо требований. Вы можете видеть, что f( ) может быть применен как к объекту класса, наследуемому от Simple, так и не наследуемому он него, без какой-либо дискриминации. Если вы программист на C++, вы должны заметить, что здесь реализована особенность шаблонов C++: для предоставления слабой типизации в строго типизированном языке. Таким образом, в Python вы автоматически получаете эквивалент шаблонов - без необходимости изучать этот особенно трудный синтаксис и семантику.

Создание языка

Оказывается, невероятно просто использовать Jython для создания интерпретируемого языка внутри вашего приложения. Рассмотрим пример диспетчера оранжереи, приведенный в Главе 8 книги Thinking in Java, 2nd edition. Это ситуация, в которой вы хотите, чтобы конечный пользователь - персона, управляющая оранжереей - имел возможность конфигурирования управления системой и простой скриптовый язык является идеальным решением.

Для создания языка мы просто напишем несколько Python'овских классов, а конструктор каждого из них добавит себя в (статический) список. Общие данные и поведение будут встроены в базовый класс Event. Каждый объект Event будет содержать строку action (для упрощения - в реальности, вы должны предоставить некоторую функциональность) и время, когда событие предназначается к запуску. Конструктор инициализирует эти поля, а замет добавляет новый объект Event в статический список, называемый events (определен в классе, но снаружи методов, что делает его статическим):

#:interpreter:GreenHouseLanguage.py

class Event:
  events =
[] # статический
  def __init__
(self, action, time):
    self.action = action
    self.time = time
    Event.events.append
(self)
 
# Используется функцией sort(). Это необходимо
  # сравнивать, основываясь только на времени:
  def __cmp__
(self, other):
   
if self.time < other.time: return -1
   
if self.time > other.time: return 1
   
return 0
 
def run(self):
    print
"%.2f: %s" % (self.time, self.action)

class LightOn(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Light on", time)

class LightOff(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Light off", time)

class WaterOn(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Water on", time)

class WaterOff(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Water off", time)

class ThermostatNight(Event):
  def __init__
(self, time):
    Event.__init__
(self,"Thermostat night", time)

class ThermostatDay(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Thermostat day", time)

class Bell(Event):
  def __init__
(self, time):
    Event.__init__
(self, "Ring bell", time)

def run():
  Event.events.sort
();
 
for e in Event.events:
    e.run
()

# Для провреки это должно быть запущено командой:
# python GreenHouseLanguage.py
if __name__ == "__main__":
  ThermostatNight
(5.00)
 
LightOff(2.00)
 
WaterOn(3.30)
 
WaterOff(4.45)
 
LightOn(1.00)
 
ThermostatDay(6.00)
 
Bell(7.00)
 
run()
##~

Конструктор каждого наследуемого класса вызывает конструктор базового класса, который добавляет новый объект в список. Функция run( ) сортирует список, при этом автоматически используется метод __cmp__( ), который определен в базовом классе Event и сравнивает только по времени. В этом примере просто печатается список, но в реальной системе необходимо ждать наступления момента времени для каждого объекта, а затем запускать событие.

Секция __main__ выполняет простой тест классов.

Приведенный выше файл теперь является модулем, который может быть включен в другую Python программу для определения всех классов, которые он содержит. Но вместо обычной Python программы давайте будем использовать Jython внутри Java. Это можно сделать чрезвычайно просто: вы импортируете некоторые классы Jython, создаете объект PythonInterpreter, и это является причиной загрузки Python файлов:

//- interpreter:GreenHouseController.java

package interpreter;

import org.python.util.PythonInterpreter;

import org.python.core.*;

import junit.framework.*;



public class GreenHouseController extends TestCase  {
 
PythonInterpreter interp = new PythonInterpreter();

 
public void test() throws PyException  {
   
System.out.println("Loading GreenHouse Language");
    interp.execfile
("GreenHouseLanguage.py");
    System.out.println
("Loading GreenHouse Script");
    interp.execfile
("Schedule.ghs");
    System.out.println
("Executing GreenHouse Script");
    interp.exec
("run()");
 
}

 
public static void main(String[] args) throws PyException  {
   
junit.textui.TestRunner.run(GreenHouseController.class);
 
}

}
///:~

Объект PythonInterpreter является полным интерпретатором Python, который принимает команды из Java программы. Одной из этих команд является execfile( ), которая просит выполнить все выражения, которые можно найти в определенном файле. При выполнении GreenHouseLanguage.py все классы из этого файла загружаются в наш объект PythonInterpreter, и он теперь "хранит" язык управления оранжереей. Файл Schedule.ghs представляет собой файл, созданный конечным пользователем для управления оранжереей. Вот его пример:

//:! interpreter:Schedule.ghs
Bell(7.00)
ThermostatDay(6.00)
WaterOn(3.30)
LightOn(1.00)
ThermostatNight(5.00)
LightOff(2.00)
WaterOff(4.45)
///:~

Это цель шаблона проектирования итератора: сделать конфигурацию вашей программы настолько простой, насколько это возможно для конечного пользователя. С помощью Jython вы можете достигнуть этого практически без каких-либо усилий.

Один из методов, доступных у объекта PythonIterator, является метод exec( ), который позволяет вам посылать команды интерпретатору. Здесь функция run( ) вызывается с использованием exec( ).

Помните, что для запуска этой программы вы должны посетить http://jython.sourceforge.net, загрузить и установить Jython (фактически, вам необходим только jython.jar в вашем CLASSPATH). Как только он будет на месте, он может быть запущен как любая другая Java программа.

Управление интерпретатором

Предыдущий пример только создавал и запускал интерпретатор при помощи использования внешних скриптов. В оставшейся части главы мы взглянем на более изощренные способы взаимодействия с Jython. Наиболее простой способ получить больший контроль над объектом PythonInterpreter изнутри Java является в посылке данных интерпретатору и получении данных обратно.

Передача данных

Для вставки данных в вашу Python программу класс PythonInterpreter имеет обманчиво простотой метод : set( ). Однако, set( ) принимает много различных типов данных и выполняет конвертирование меду ними. Приведенный ниже пример показывает практически полный набор вариантов, возможных для set( ), наряду с комментариями, которые дают довольно полное объяснение:

//- interpreter:PythonInterpreterSetting.java
// Передача данных из Java в Python, когда
// используется объект PythonInterpreter.
package interpreter;

import org.python.util.PythonInterpreter;

import org.python.core.*;

import java.util.*;

import com.bruceeckel.python.*;

import junit.framework.*;

public class PythonInterpreterSetting extends TestCase {
  
PythonInterpreter interp = new PythonInterpreter();
  
  
public void test() throws PyException {
     
// Он автоматически конвертирует Strings
      // в нативные строки Python:
     
interp.set("a", "This is a test");
      interp.exec
("print a");
      interp.exec
("print a[5:]"); // Срез
      // Он так же знает, что делать с массивами:
     
String[] s = { "How", "Do", "You", "Do?" };
      interp.set
("b", s);
      interp.exec
("for x in b: print x[0], x");
     
// set() принимает только Objects, так что он не может
      // принять примитивные типы. Вместо этого
      // вы используете обертки:
     
interp.set("c", new PyInteger(1));
      interp.set
("d", new PyFloat(2.2));
      interp.exec
("print c + d");
     
// Вы также можете использовать Java-обертки:
     
interp.set("c", new Integer(9));
      interp.set
("d", new Float(3.14));
      interp.exec
("print c + d");
     
// Определяем в Python'е функцию для пенчати массивов:
     
interp.exec("def prt(x): n" + "  print x n" + "  for i in x: n"
           
+ "    print i, n" + "  print x.__class__n");
     
// Массивы являются Объектами, так что нет проблем
      // в передаче типов, содержащихся в массиве:
     
Object[] types = { new boolean[] { true, false, false, true },
           
new char[] { 'a', 'b', 'c', 'd' }, new byte[] { 1, 2, 3, 4 },
           
new int[] { 10, 20, 30, 40 },
           
new long[] { 100, 200, 300, 400 },
           
new float[] { 1.1f, 2.2f, 3.3f, 4.4f },
           
new double[] { 1.1, 2.2, 3.3, 4.4 }, };
     
for (int i = 0; i < types.length; i++) {
        
interp.set("e", types[i]);
         interp.exec
("prt(e)");
     
}
     
// Он использует toString() для печати Java объектов:
     
interp.set("f", new Date());
      interp.exec
("print f");
     
// Вы можете передать ему List
      // и индекс по нему...
     
List x = new ArrayList();
     
for (int i = 0; i < 10; i++)
        
x.add(new Integer(i * 10));
      interp.set
("g", x);
      interp.exec
("print g");
      interp.exec
("print g[1]");
     
// ...Но он не достаточно умен, чтобы
      // трактовать его, как массив в Python:
     
interp.exec("print g.__class__");
     
// interp.exec("print g[5:]); // Ошибка
      // Если вы хотите массив в python, вы
      // должны получить массив Java:
     
System.out.println("ArrayList to array:");
      interp.set
("h", x.toArray());
      interp.exec
("print h.__class__");
      interp.exec
("print h[5:]");
     
// Мередача Map:
     
Map m = new HashMap();
      m.put
(new Integer(1), new Character('a'));
      m.put
(new Integer(3), new Character('b'));
      m.put
(new Integer(5), new Character('c'));
      m.put
(new Integer(7), new Character('d'));
      m.put
(new Integer(11), new Character('e'));
      System.out.println
("m: " + m);
      interp.set
("m", m);
      interp.exec
("print m, m.__class__, " + "m[1], m[1].__class__");
     
// Это не словарь Python, поэтому не работает:
      //! interp.exec("for x in m.keys():" +
      //!   "print x, m[x]");
      // Чтобы конвертировать Map в словарь Python,
      // используйте com.bruceeckel.python.PyUtil:
     
interp.set("m", PyUtil.toPyDictionary(m));
      interp.exec
("print m, m.__class__, " + "m[1], m[1].__class__");
      interp.exec
("for x in m.keys():print x,m[x]");
  
}
  
  
public static void main(String[] args) throws PyException {
     
junit.textui.TestRunner.run(PythonInterpreterSetting.class);
  
}
}
///:~

Как обычно в Java различия между реальными объектами и примитивными типами становится причиной проблем. В общем случае, если вы передаете обычный объект в set( ), он знает что с ним делать, но если вы хотите передать примитивный тип, вы должны выполнить конвертирование. Один из способов сделать это - создать "Py" типы, такие как PyInteger или PyFloat, но с другой стороны, вы можете использовать собственные Java объекты-обертки, такие как Integer и Float, что гораздо легче запомнить.

В начале программы вы видите exec( ), содержащий выражение Python:

print a[5:]

Двоеточие внутри выражения индекса указывает Python срез, который воспроизводит диапазон элементов из обычного массива. В этом случае, он производит массив, содержащий элементы с номера 5 до конца массива. Вы можете также легко сказать "a[3:5]" для производства элементов с третьего по 5, или "a[:5]" для производства элементов с нулевого по пятый. Смысл использования среза в этом выражении состоит в том, чтобы убедиться, что Java Strings реально будут конвертированы в строки Python, которые также могут быть трактуемы, как массивы символов.

Так же вы можете видеть, что с помощью exec( ) можно создать функцию Python (хотя это немного неудобно). Функция prt( ) печатает весь массив, а затем (чтобы убедиться, что это действительно массив Python) проходит по каждому элементу массива и печатает его. В конце печатается класс массива, так что мы можем видеть, какое преобразование имело место (Python имеет не только информацию времени выполнения, он также имеет эквивалент Java рефлексии). Функция prt( ) используется для печати массивов, которые составляются из примитивных типов Java.

Хотя ArrayList Java передается в интерпретатор с помощью set( ), и вы можете передвигаться по нему с помощью индекса, как будто бы это был массив, попытка создать срез проваливается. Чтобы полностью конвертировать его в массив, один из подходов состоит с простой вытяжке массива с помощью toArray( ) и передаче результата. Метод set( ) конвертирует его в PyArray - один из классов, предоставляемых Jython - который может быть трактован, как массив Python (вы можете также явно создать PyArray, но в этом нет необходимости).

В конце создается Map и передается напрямую в интерпретатор. Пока возможно сделать такие простые вещи, как индексирование результирующего объекта, реально это не словарь Python, так что вы не можете (например) вызвать метод keys( ). Нет прямого способа конвертировать Java Map в словарь Python, так что я написал утилиту, которая называется toPyDictionary( ) и сделал этот метод статическим в com.bruceeckel.python.PyUtil. Здесь также содержатся утилиты для вытяжки массива Python в Java List и словаря Python в Java Map:

//- com:bruceeckel:python:PyUtil.java
// PythonInterpreter
package com.bruceeckel.python;

import org.python.util.PythonInterpreter;

import org.python.core.*;

import java.util.*;

public class PyUtil {
  
/**
    * Extract a Python tuple or array into a Java
    *
    * List (which can be converted into other kinds
    *
    * of lists and sets inside Java).
    *
    *
@param interp
    *            The Python interpreter object
    *
    *
@param pyName
    *            The id of the python list object
    *
    */
  
public static List toList(PythonInterpreter interp, String pyName) {
     
return new ArrayList(Arrays.asList((Object[]) interp.get(pyName,
            Object
[].class)));
  
}
  
  
/**
    * Extract a Python dictionary into a Java Map
    *
    *
@param interp
    *            The Python interpreter object
    *
    *
@param pyName
    *            The id of the python dictionary
    *
    */
  
public static Map toMap(PythonInterpreter interp, String pyName) {
     
PyList pa = ((PyDictionary) interp.get(pyName)).items();
      Map map =
new HashMap();
     
while (pa.__len__() != 0) {
        
PyTuple po = (PyTuple) pa.pop();
         Object first = po.__finditem__
(0).__tojava__(Object.class);
         Object second = po.__finditem__
(1).__tojava__(Object.class);
         map.put
(first, second);
     
}
     
return map;
  
}
  
  
/**
    * Turn a Java Map into a PyDictionary,
    *
    * suitable for placing into a PythonInterpreter
    *
    *
@param map
    *            The Java Map object
    *
    */
  
public static PyDictionary toPyDictionary(Map map) {
     
Map m = new HashMap();
      Iterator it = map.entrySet
().iterator();
     
while (it.hasNext()) {
        
Map.Entry e = (Map.Entry) it.next();
         m.put
(Py.java2py(e.getKey()), Py.java2py(e.getValue()));
     
}
     
// PyDictionary constructor wants a Hashtable:
     
return new PyDictionary(new Hashtable(m));
  
}
}
// /:~

Вот код теста модуля (черный ящик):

//- com:bruceeckel:python:Test.java
package com.bruceeckel.python;

import org.python.util.PythonInterpreter;

import java.util.*;

import junit.framework.*;

public class Test extends TestCase {
  
PythonInterpreter pi = new PythonInterpreter();
  
  
public void test1() {
     
pi.exec("tup=('fee','fi','fo','fum','fi')");
      List lst = PyUtil.toList
(pi, "tup");
      System.out.println
(lst);
      System.out.println
(new HashSet(lst));
  
}
  
  
public void test2() {
     
pi.exec("ints=[1,3,5,7,9,11,13,17,19]");
      List lst = PyUtil.toList
(pi, "ints");
      System.out.println
(lst);
  
}
  
  
public void test3() {
     
pi.exec("dict = { 1 : 'a', 3 : 'b', " + "5 : 'c', 9 : 'd', 11 : 'e'}");
      Map mp = PyUtil.toMap
(pi, "dict");
      System.out.println
(mp);
  
}
  
  
public void test4() {
     
Map m = new HashMap();
      m.put
("twas", new Integer(11));
      m.put
("brillig", new Integer(27));
      m.put
("and", new Integer(47));
      m.put
("the", new Integer(42));
      m.put
("slithy", new Integer(33));
      m.put
("toves", new Integer(55));
      System.out.println
(m);
      pi.set
("m", PyUtil.toPyDictionary(m));
      pi.exec
("print m");
      pi.exec
("print m['slithy']");
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(Test.class);
  
}
}
// /:~

Мы увидим использование инструментов вытяжки в следующем разделе.

Получение данных

Есть несколько различных способов получить данные из PythonInterpreter. Если вы просто вызовите метод get( ), передав ему идентификатор объекта в виде строки, он вернет PyObject (часть классов поддержки org.python.core). Возможно "привести (cast)" его с помощью метода __tojava__( ), но есть лучшие альтернативы:

  1. Удобный метод класса Py, такой как py2int( ), принимающий PyObject и преобразующий его в несколько различных типов.

  2. Перегруженная версия get( ), принимающая определенный Java объект Class в качестве второго аргумента, и производящая объект, который получает этот тип во время выполнения (так что вам все еще нужно выполнить приведение результата в вашем Java коде).

Использование второго подхода при получении массива из PythonInterpreter гораздо легче. Это особенно полезно, потому что Python исключительно удобен при манипуляции строками и файлами, так что чаще всего вы хотите получить результат, как массив строк. Например, вы можете сделать расширение имен файлов, используя Python glob( ), как это показано в приведенном ниже коде:

//- interpreter:PythonInterpreterGetting.java
// Getting data from the PythonInterpreter object.
package interpreter;

import org.python.util.PythonInterpreter;

import org.python.core.*;

import java.util.*;

import com.bruceeckel.python.*;

import junit.framework.*;

public class PythonInterpreterGetting extends TestCase {
  
PythonInterpreter interp = new PythonInterpreter();
  
  
public void test() throws PyException {
     
interp.exec("a = 100");
     
// Если вы используете обычный get(),
      // он возвращает PyObject:
     
PyObject a = interp.get("a");
     
// Вы не многое можете сделать с общим
      // PyObject, но вы можете его напечатать:
     
System.out.println("a = " + a);
     
// Если вы знаете предполагаемый тип,
      // вы можете "привести" с помощью __tojava__()
      // к тому Java типу и манипулировать им в Java.
      // Для использования 'a' в качестве int, вы должны
      // использовать класс-обертку Integer:
     
int ai = ((Integer) a.__tojava__(Integer.class)).intValue();
     
// Есть также более удобная функция:
     
ai = Py.py2int(a);
      System.out.println
("ai + 47 = " + (ai + 47));
     
// Вы можете конвертировать в различные типы:
     
float af = Py.py2float(a);
      System.out.println
("af + 47 = " + (af + 47));
     
// Еслы вы попробуете привести к неподходящему
      // типу, вы получите исключение времени выполнения:
      // ! String as = (String)a.__tojava__(
      // ! String.class);
      // Если вы знаете тип, более полезный метод - это
      // перегруженный get(), принимающий нужный вам класс
      // в качестве второго аргумента:
     
interp.exec("x = 1 + 2");
     
int x = ((Integer) interp.get("x", Integer.class)).intValue();
      System.out.println
("x = " + x);
     
// Поскольку Python хорош для манипуляции строками
      // и файлами, вам часто будет необходимо вытянуть массив
      // из Strings.Вот файл, который читается, как
      // массив Python:
     
interp.exec("lines = " + "open('PythonInterpreterGetting.java')"
           
+ ".readlines()");
     
// Получам его, как массив Java из String:
     
String[] lines = (String[]) interp.get("lines", String[].class);
     
for (int i = 0; i < 10; i++)
        
System.out.print(lines[i]);
     
// В качестве примера полезности строкового инструмена,
      // глобальный поиск имен файлов
      // с использованием glob - очень полезен, но он не является
      // частью стандарного пакета Jython, так что
      // вам нужно убедиться, что ваш путь
      // Python установлен и включает библиотеку или что вы
      // доставили необходимые Python файлы
      // с вашим приложением.
     
interp.exec("from glob import glob");
      interp.exec
("files = glob('*.java')");
      String
[] files = (String[]) interp.get("files", String[].class);
     
for (int i = 0; i < files.length; i++)
        
System.out.println(files[i]);
     
// Вы можете вытянуть записи и массивы в
      // Java Lists с мопощью com.bruceeckel.PyUtil:
     
interp.exec("tup = ('fee', 'fi', 'fo', 'fum', 'fi')");
      List tup = PyUtil.toList
(interp, "tup");
      System.out.println
(tup);
     
// Реальный список объектов String:
     
System.out.println(tup.get(0).getClass());
     
// Вы можете легко конвертировать в Set:
     
Set tups = new HashSet(tup);
      System.out.println
(tups);
      interp.exec
("ints=[1,3,5,7,9,11,13,17,19]");
      List ints = PyUtil.toList
(interp, "ints");
      System.out.println
(ints);
     
// Это реальный List из объектов Integer:
     
System.out.println((ints.get(1)).getClass());
     
// Если у вас есть словарь Python, его можно
      // вытянуть в Java Map, опять таки с помощью
      // com.bruceeckel.PyUtil:
     
interp.exec("dict = { 1 : 'a', 3 : 'b',"
           
+ "5 : 'c', 9 : 'd', 11 : 'e' }");
      Map map = PyUtil.toMap
(interp, "dict");
      System.out.println
("map: " + map);
     
// Это реальные объекты Java, не PyObjects:
     
Iterator it = map.entrySet().iterator();
      Map.Entry e =
(Map.Entry) it.next();
      System.out.println
(e.getKey().getClass());
      System.out.println
(e.getValue().getClass());
  
}
  
  
public static void main(String[] args) throws PyException {
     
junit.textui.TestRunner.run(PythonInterpreterGetting.class);
  
}
}
// /:~

Последние два примера показывают вытягивание записей и списков Python в Java List, а словарей Python в Java Map. Оба эти случая требуют большей обработки по сравнению с той, которая предоставляется стандартной библиотекой Jython, так что я опять создал утилиты в com.bruceeckel.python.PyUtil:toList( ) для производства List из последовательности Python, а toMap( ) производит Map из словаря Python. Методы PyUtil облегчают импорт структур данных туда и обратно между Java и Python.

Множественные интерпретаторы

Также ничего не стоит создать множество объектов PythonInterpreter в программе, и каждая из них будет иметь свое собственное пространство имен:

//- interpreter:MultipleJythons.java
// Вы можете запустить несколько интерпретаторов,
// каждый со своим пространством имен.
package interpreter;

import org.python.util.PythonInterpreter;

import org.python.core.*;

import junit.framework.*;

public class MultipleJythons extends TestCase {
  
PythonInterpreter interp1 = new PythonInterpreter(),
         interp2 =
new PythonInterpreter();
  
  
public void test() throws PyException {
     
interp1.set("a", new PyInteger(42));
      interp2.set
("a", new PyInteger(47));
      interp1.exec
("print a");
      interp2.exec
("print a");
      PyObject x1 = interp1.get
("a");
      PyObject x2 = interp2.get
("a");
      System.out.println
("a from interp1: " + x1);
      System.out.println
("a from interp2: " + x2);
  
}
  
  
public static void main(String[] args) throws PyException {
     
junit.textui.TestRunner.run(MultipleJythons.class);
  
}
}
// /:~

Когда вы запустите программу, вы увидите, что значение a отличается для каждого PythonInterpreter.

Контролирование Java из Jython

Так как вы имеете в своем распоряжении Java, и вы можете установить и получить значение для интерпретатора, есть огромное количество того, что вы можете выполнить, используя описанный ранее подход (управляя Python из Java). Но одна из удивительных вещей относительно Jython состоит в том, что он делает Java классы почти прозрачно доступными изнутри Jython. Обычно Java классы выглядят, как и Python классы. Это верно для классов из стандартных библиотек Java так же точно, как и для классов, который вы создаете сами, как это видно в приведенном ниже примере:

## interpreter:JavaClassInPython.py
#=M jython.bat JavaClassInPython.py
# Использование классов Java изнутри Jython
from java.util
import Date, HashSet, HashMap
from interpreter.javaclass
import JavaClass
from math
import sin

d = Date
() # Создание объекта Java Date
print d # Вызов toString
()

# "Генератор" легко создаст данные:
class ValGen:
  def __init__
(self, maxVal):
    self.val = range
(maxVal)
 
# Вызывается во время итерации 'for':
 
def __getitem__(self, i):
    # Возвращается запись из двух элементов:
   
return self.val[i], sin(self.val[i])

# Стандартные Java контейнеры:
map = HashMap
()
set = HashSet()

for x, y in ValGen(10):
  map.put
(x, y)
 
set.add(y)
 
set.add(y)
print map
print set

# Итерация по множеству:
for z in set:
  print z, z.__class__

print map
[3] # Используем индекситование Python словаря
for x in map.keySet(): # keySet() метод из Map
  print x, map
[x]

# Использование классов Java, которые вы создали сами,
# очень просто:
jc = JavaClass
()
jc2 = JavaClass("Created within Jython")
print jc2.getVal()
jc.setVal("Using a Java class is trivial")
print jc.getVal()
print jc.getChars()
jc.val = "Using bean properties"
print jc.val
##~

Комментарий "=M" распознается инструментом генерации mekefile'а (который я создал для этой книги) для замены команды makefile. Он будет использоваться взамен команд, которые инструмент извлечения обычно помещает в makefile.

Обратите внимание, что выражение import относится к структуре пакетов Java именно так, как этого следовало ожидать. В первом примере создается объект Date( ), как будто это родной класс Python, и происходит распечатка этого объекта простым вызовом toString( ).

ValGen реализует концепцию "генератора", который использует отличную идею из C++ STL (Стандартная Библиотека Шаблонов, часть стандартной библиотеки C++). Генератор является объектом, который производит новый объект при каждом вызове "метода генерации", и он достаточно удобен для заполнения контейнеров. Далее я использую его в итераторе for, так что мне нужен метод генерации, чтобы он вызывался в процессе итерации. Это специальный метод, называемый __getitem__( ), который фактически является перегруженным оператором для индексирования '[ ]'. В цикле for этот метод вызывается каждый раз, когда есть необходимость продвинутся к следующему элементу, а когда элементы заканчиваются, __getitem__( ) выбрасывает исключение выхода за пределы диапазона, тем самым сигнализируя об окончании цикла for (в других языках вы никогда не использовали исключения для обычного управления процессом выполнения, но в Python это выглядит достаточно хорошо). Это исключение возникает автоматически, когда self.val[i] выходит за пределы элементов, что упрощает код __getitem( ). Сложность заключается только в том, что __getitem__( ) казалось бы возвращает два объекта вместо одного. Python автоматически пакует множественные возвращаемые значения в запись (структуру), так что с другой стороны вы получаете единый объект (в C++ или Java вы должны создавать собственную структуру данных, чтобы сделать это). Кроме того, в цикле for, где используется Valgen, Python автоматически "распаковывает" запись, так что вы имеете множество итераторов в цикле for. Упрощения синтаксиса такого рода делает Python столь любимым.

Объекты map и set являются экземплярами Java классов HasMap и HashSet, опять таки созданных так, как будто они являются родными компонентами Python. В цикле for методы put( ) и add( ) работают так же, как это делается в Java. Так что индексирование в Java Map использует ту же нотацию, что и для словарей, но заметьте, что для прохождения по ключам Map вы должны использовать метод Map keySet( ), а не метод keys( ) для словарей Python.

Заключительная часть примера показывает использование Java класса, который я создал для интереса, чтобы продемонстрировать, как это просто. Обратите внимание, что Jython интуитивно понимает свойства JavaBean, так что вы можете использовать методы getVal( ) и setVal( ) или присваивать и читать из эквивалентного val-свойства. Кроме того, getChars( ) возвращает Character[] в Java, который становится массивом в Python.

Простейший способ использования Java классов, которые вы создали, внутри Python программ состоит в помещении их внутрь пакетов. Хотя Jython может также импортировать Java классы не входящие в пакет (import JavaClass), все такие не входящие в пакет классы будут трактоваться, как если бы они были определены в различных пакетах, так что они смогут видеть только публичные методы друг друга.

Java пакеты транслируются в модули Python, и Python должен импортировать модули, чтобы быть способным использовать классы Java. Ниже приведен код для JavaClass:

//- interpreter:javaclass:JavaClass.java
package interpreter.javaclass;

import junit.framework.*;

import com.bruceeckel.util.*;

public class JavaClass {
  
private String s = "";
  
  
public JavaClass() {
     
System.out.println("JavaClass()");
  
}
  
  
public JavaClass(String a) {
     
s = a;
      System.out.println
("JavaClass(String)");
  
}
  
  
public String getVal() {
     
System.out.println("getVal()");
     
return s;
  
}
  
  
public void setVal(String a) {
     
System.out.println("setVal()");
      s = a;
  
}
  
  
public Character[] getChars() {
     
System.out.println("getChars()");
      Character
[] r = new Character[s.length()];
     
for (int i = 0; i < s.length(); i++)
        
r[i] = new Character(s.charAt(i));
     
return r;
  
}
  
  
public static class Test extends TestCase {
     
JavaClass x1 = new JavaClass(), x2 = new JavaClass("UnitTest");
     
     
public void test1() {
        
System.out.println(x2.getVal());
         x1.setVal
("SpamEggsSausageAndSpam");
         System.out.println
(Arrays2.toString(x1.getChars()));
     
}
   }
  
  
public static void main(String[] args) {
     
junit.textui.TestRunner.run(Test.class);
  
}
}
// /:~

Вы можете видеть, что это обычный Java класс без каких-либо указаний, что он будет использоваться в Jython программе. По этой причине один из важнейiих этапов использования Jython состоит в тестировании Java кода [10]. Поскольку Python является достаточно мощным, гибким, динамичным языком - это идеальный инструмент для автоматизированного тестирования рабочей среды без добавления и изменения тестируемого Java кода.

Внутренние классы

Внутренние классы становятся атрибутами объекта класса. Экземпляры статического внутреннего класса могут быть созданы при помощи обычного вызова:

com.foo.JavaClass.StaticInnerClass()

Не статические внутренние классы должны иметь внешний экземпляр класса, указываемый как первый аргумент:

com.foo.JavaClass.InnerClass(com.foo.JavaClass())

Использование библиотек Java

Jython оборачивает Java библиотеки так, что любая из них может быть использована напрямую или через наследование. Кроме того, Python стенографически упрощает кодирование.

В качестве иллюстрации рассмотрим пример HTMLButton.java из 13 главы Thinking in Java, 2nd edition (вероятно вы уже загрузили и установили исходный код этой книги с сайта www.BruceEckel.com, так как некоторые примеры этой книги используют библиотеки из этой книги). Вот версия для Jython:

## interpreter:PythonSwing.py
#
Пример HTMLButton.java из
#
"Thinking in Java, 2nd edition," Chapter 13,
# конвертированный в Jython.
# Не запускайте это как часть автоматической сборки:
#=M
@echo skipping PythonSwing.py
from javax.swing
import JFrame, JButton, JLabel
from java.awt
import FlowLayout

frame = JFrame
("HTMLButton", visible=1,
  defaultCloseOperation=JFrame.EXIT_ON_CLOSE
)


def kapow
(e):
  frame.contentPane.add
(JLabel("<html>"+
   
"<i><font size=+4>Kapow!"))
 
# Заставляем перераспределить,
  # чтобы включить новую метку:
  frame.validate
()

button = JButton("<html><b><font size=+2>" +
 
"<center>Hello!<br /><i>Press me now!",
  actionPerformed=kapow
)
frame.contentPane.layout = FlowLayout()
frame.contentPane.add(button)
frame.pack()
frame.size=200, 500
##~

Если вы сравните Java версию программы с приведенной выше реализацией на Jython, вы увидите, что Jython короче и в общем легче для понимания. Например, в Java версии для настройки фрейма вы делали несколько вызовов: конструктора JFrame( ), метода setVisible( ) и метода setDefaultCloseOperation( ), в приведенном выше примере эти три операции выполняются в единственном вызове конструктора.

Также обратите внимание, что JButton сконфигурирован с методом actionListener( ) внутри конструктора с присвоением kapow. Кроме того, осведомленность Jython'а о JavaBean означает, что вызов любого метода с именем, начинающегося с "set", может быть заменено простым присвоением, как вы видите выше.

Единственный метод, не перешедший из Java - это метод pack( ), который, кажется, является неотъемлемой частью для запуска правильного переразмещения. Также очень важно, чтобы вызов pack( ) произошел прежде установки размеров.

Наследование от библиотечных классов Java

Вы легко можете наследовать от стандартных библиотечных классов Java в Jython. Вот пример Dialogs.java из Chapter 13 Thinking in Java, 2nd edition, конвертированный в Jython:

## interpreter:PythonDialogs.py
# Dialogs.java from
"Thinking in Java, 2nd
# edition,"
Chapter 13, converted into Jython.
# :
#=M
@echo skipping PythonDialogs.py
from java.awt
import FlowLayout
from javax.swing
import JFrame, JDialog, JLabel
from javax.swing
import JButton

class MyDialog(JDialog):
  def __init__
(self, parent=None):
    JDialog.__init__
(self,title="My dialog", modal=1)
   
self.contentPane.layout = FlowLayout()
   
self.contentPane.add(JLabel("A dialog!"))
   
self.contentPane.add(JButton("OK",
      actionPerformed = lambda e, t=self: t.dispose
()))
   
self.pack()

frame = JFrame("Dialogs", visible=1,
  defaultCloseOperation=JFrame.EXIT_ON_CLOSE
)
dlg = MyDialog()
frame.contentPane.add(
 
JButton("Press here to get a Dialog Box",
    actionPerformed = lambda e: dlg.show
()))
frame.pack()
##~


MyDialog
наследуется от JDialog, и вы можете видеть поименованный аргумент, использованный в вызове конструктора базового класса.

Обратите внимание, что при создании JButton "ОК" метода actionPerformed устанавливается внутри конструктора, и что функция создается при использовании ключевого слова Python lambda. Здесь создается безымянная функция с аргументом, стоящим перед двоеточием, и выражением, которое генерирует возвращаемое значение, после двоеточия. Как вы должны знать, Java прототип для метода actionPerformed( ) содержит единственный аргумент, но выражение lambda указывает два. Однако второму аргументу предоставлено значение по умолчанию, так что функция может быть вызвана только с одним аргументом. По этой причине второй аргумент указан со значением по умолчанию, поскольку это способ передать self в lambda выражение, которое может быть использовано для уничтожения диалога.

Сравните этот код с версией, которая опубликована в Thinking in Java, 2nd edition. Вы обнаружите, что особенности языка Python позволяют сильно сжать и направить реализацию.

Создание Java классов в Jython

Хотя это напрямую не относится к начальной проблеме этой главы (создание интерпретаторов), Jython имеет дополнительные возможности для создания Java классов напрямую из вашего Jython кода. Это может дать очень полезные результаты, поскольку вы можете трактовать результат, как родной Java класс, хотя находящийся во власти Python.

Для воспроизводства Java класса из кода Python, Jython поставляется с компилятором, называемым jythonc.

Процесс создания класса Python, который может произвести Java классы немного более сложный, чем при вызове Java классов из Python, поскольку методы в Java классах строго типизированы, в то время, как функции Python и методы имеют слабую типизацию. Таким образом, вы должны каким-то образом сказать jythonc, что метод Python предназначен для определенного набора аргументов и что он возвращает значение определенного типа. Вы можете сделать это с помощью строки "@sig", которая помещается сразу за началом определения Python метода (это стандартное расположение для строк документирования Python). Например:

def returnArray(self):
   
"@sig public java.lang.String[] returnArray()"

Определение Python не указывает какого-либо типа возвращаемого аргумента, но строка @sig дает полную информацию о типах, которые передаются и возвращаются. Компилятор jythonc использует эту информацию для генерации корректного Java кода.

Есть еще набор правил, которым вы должны следовать для достижения успешной компиляции: вы должны наследовать ваш Python класс от Java класса или интерфейса (вам не нужно указывать сигнатуру @sig для методов, определенных в родительском классе/интерфейсе). Если вы не сделаете этого, вы не сможете получить желаемые методы - к сожалению, jythonc не выдает предупреждений или ошибок в этом случае, но вы не сможете получить того, что хотите. Если вы не видите, что отсутствует, это может очень расстроить.

Кроме того, вы должны импортировать соответствующие Java классы и дать корректное указание пакета. В приведенном выше примере импортируется java, так что вы должны наследовать от java.lang.Object, но вы можете также сказать from java.lang import Object, а затем вы просто наследуете от Object без указания пакета. К сожалению, вы не получите никаких предупреждений или ошибок, если вы сделаете эту ошибку, так что вы должны быть терпеливы и продолжать пробовать.

Ниже приведен пример создания Python класса для производства Java класса. Здесь также введена директива "=T" для инструмента построения makefile, которая указывает различные цели, которые обычно преследуются инструментом. В данном случае, Python файл используется для построения Java .class файла, так что файл класса является желанной целью makefile. Чтобы сделать это, команда mekefile по умолчанию заменяется с помощью директивы "=M" (обратите внимание, как вы можете разбить линии, используя ''):

## interpreter:PythonToJavaClass.py
#=T pythonjavatestPythonToJavaClass.
class
#=M jythonc.bat --package python.java.test
#=M PythonToJavaClass.py
# Создание Python класса для производства Java класса
from jarray
import array
import java

class PythonToJavaClass(java.lang.Object):
  # Строка сигнатуры
'@sig' используется для создания
  # правильной сигнатуры в результирующем
  # Java коде:
  def __init__
(self):
   
"@sig public PythonToJavaClass()"
   
print "Constructor for PythonToJavaClass"

 
def simple(self):
   
"@sig public void simple()"
   
print "simple()"

 
# Возвращаемы значения для Java:
  def returnString
(self):
   
"@sig public java.lang.String returnString()"
   
return "howdy"

 
# Вы должны создать массивы для возвращения наряду
  # с типом массива:
  def returnArray
(self):
   
"@sig public java.lang.String[] returnArray()"
   
test = [ "fee", "fi", "fo", "fum" ]
   
return array(test, java.lang.String)

 
def ints(self):
   
"@sig public java.lang.Integer[] ints()"
   
test = [ 1, 3, 5, 7, 11, 13, 17, 19, 23 ]
   
return array(test, java.lang.Integer)

 
def doubles(self):
   
"@sig public java.lang.Double[] doubles()"
   
test = [ 1, 3, 5, 7, 11, 13, 17, 19, 23 ]
   
return array(test, java.lang.Double)

 
# Передача аргументов из Java:
  def argIn1
(self, a):
   
"@sig public void argIn1(java.lang.String a)"
   
print "a: %s" % a
    print
"a.__class__", a.__class__

  def argIn2
(self, a):
   
"@sig public void argIn1(java.lang.Integer a)"
   
print "a + 100: %d" % (a + 100)
   
print "a.__class__", a.__class__

  def argIn3
(self, a):
   
"@sig public void argIn3(java.util.List a)"
   
print "received List:", a, a.__class__
    print
"element type:", a[0].__class__
    print
"a[3] + a[5]:", a[5] + a[7]
   
#! print "a[2:5]:", a[2:5] # Не работает
   
  def argIn4
(self, a):
   
"@sig public void
       argIn4(org.python.core.PyArray a)"
   
print "received type:", a.__class__
    print
"a: ", a
    print
"element type:", a[0].__class__
    print
"a[3] + a[5]:", a[5] + a[7]
   
print "a[2:5]:", a[2:5] #Реальный массив Python
   
  # Карта должна передаваться, как PyDictionary:
  def argIn5
(self, m):
   
"@sig public void
       argIn5(org.python.core.PyDictionary m)"
   
print "received Map: ", m, m.__class__
    print
"m['3']:", m['3']
   
for x in m.keys():
      print x, m
[x]
##~

Прежде всего, обратите внимание, что PythonToJavaClass наследуется от java.lang.Object. Если вы не сделаете этого, вы спокойно получите Java класс без правильной сигнатуры. Вам не обязательно наследовать от Object. Любой Java класс подойдет.

Этот класс предназначен для демонстрации различных аргументов и возвращаемых значений, целью которой является показать достаточное количество примеров, чтобы вы были способны легко создавать свои собственные строки сигнатур. Первые три из них понятны сами по себе, но обратите внимание на полную квалификацию имен Java в строке сигнатуры.

В returnArray( ) Python массив должен быть возвращен в качестве Java массива. Чтобы сделать это необходимо использовать функцию array( ) (из модуля jarray), наряду с типом класса для результирующего массива. В любое время, когда вам нужно вернуть массив в Java, вы должны использовать array( ), как показано в методах ints( ) и doubles( ).

Последние методы показывают, как передавать аргументы из Java. Основные типы передаются автоматически так, как вы указываете их в строке @sig, но вы должны использовать объекты и вы не можете передать примитивные типы (то есть примитивные типы должны быть заключены в оберточные объекты, такие как Integer).

В argIn3( ) вы можете видеть, что Java List прозрачно конвертируется во что-то, что ведет себя, как массив Python, но это не полный массив, поскольку вы не можете получить его срез. Если вам нужен истинно Python массив, вы должны создать и передать PyArray, что показано в argIn4( ), где удается получить срез. Аналогично Java Map должен приходить, как PyDictionary, чтобы быть понятым, как словарь Python.

Ниже приведена Java программа, применяющая Java класс, произведенный по имеющемуся выше Python коду. Здесь также введена директива "=D" для инструмента построения makefile, который указывает зависимости в дополнение к тем, которые обнаружены инструментом. Здесь вы не можете компилировать TestPythonToJavaClass.java, пока существует PythonToJavaClass.class:

//- interpreter:TestPythonToJavaClass.java
//+D pythonjavatestPythonToJavaClass.class
package interpreter;

import java.lang.reflect.*;

import java.util.*;

import org.python.core.*;

import junit.framework.*;

import com.bruceeckel.util.*;

import com.bruceeckel.python.*;

// The package with the Python-generated classes:

import python.java.test.*;

public class TestPythonToJavaClass extends TestCase {
  
PythonToJavaClass p2j = new PythonToJavaClass();
  
  
public void testDumpClassInfo() {
     
System.out.println(Arrays2.toString(p2j.getClass().getConstructors()));
      Method
[] methods = p2j.getClass().getMethods();
     
for (int i = 0; i < methods.length; i++) {
        
String nm = methods[i].toString();
        
if (nm.indexOf("PythonToJavaClass") != -1)
           
System.out.println(nm);
     
}
   }
  
  
public void test1() {
   
p2j.simple();
    System.out.println
(p2j.returnString());
    System.out.println
(Arrays2.toString(p2j.returnArray()));
    System.out.println
(Arrays2.toString(p2j.ints());

    System.out.println
(Arrays2.toString(p2j.doubles()));
    p2j.argIn1
("Testing argIn1()");
    p2j.argIn2
(new Integer(47));
    ArrayList a =
new ArrayList();
   
for(int i = 0; i < 10; i++)
     
a.add(new Integer(i));
    p2j.argIn3
(a);
    p2j.argIn4
(new PyArray(Integer.class, a.toArray()));
    Map m =
new HashMap();
   
for(int i = 0; i < 10; i++)
     
m.put("" + i, new Float(i));
    p2j.argIn5
(PyUtil.toPyDictionary(m));
 
}
  
  
public static void main(String[] args) {
     
junit.textui.TestRunner.run(TestPythonToJavaClass.class);
  
}
}
// /:~

Для поддержки Python обычно вам достаточно импортировать классы из org.python.core. Все остальное в приведенном выше примере достаточно прямолинейно, так как появление PythonToJavaClass со стороны Java, аналогично появлению других Java классов. dumpClassInfo( ) использует рефлексию для проверки, что сигнатуры методов, указанные в PythonToJavaClass.py, были переданы верно.

Построение Java классов из кода Python

Частью фокуса по созданию Java классов из кода Python стостоит в @sig информации в строке документирования метода. Но есть вторая проблема, которая фактически основывается на том, что Python не имеет ключевого слова "package" - эквивалент пакета (модуля) в Python неявно базируется на имени файла. Однако, чтобы поместить результирующий класс-файл в Java программу jythonc должен получить информацию о том, как создавать пакет Java для кода Python. Это выполняется из командной строки jythonic при помощи флага --package, за которым следует имя пакета, который вы хотите произвести (включая разделительные точки, точно так же, как будто вы даете имя пакета, используя ключевое слово keyword в Java программе). Таким образом вы поместите результирующие .class файлы в соответствующий поддиректорий, считая от текущего директория. После этого вам необходимо только импортировать пакет в вашу Java программу, как показано выше (в вашей переменной CLASSPATH обязательно должна присутствовать ".", чтобы можно было запускать программы из текущей директории).

Вот зависимости для make, которые я использовал для построения приведенного выше примера (обратные слешы в конце строк понимаются командой make как продолжение команды на следующей строке). Эти правила расшифровываются в приведенной выше Java и Python файлах при помощи комментариев, которые понимает мой инструмент для построения makefile:

TestPythonToJavaClass.class:
        TestPythonToJavaClass.java
        pythonjavatestPythonToJavaClass.
class
   
javac TestPythonToJavaClass.java

pythonjavatestPythonToJavaClass.
class:
        PythonToJavaClass.py
    jythonc.bat --
package python.java.test
    PythonToJavaClass.py

Первая цель: TestPythonToJavaClass.class, зависит и от TestPythonToJavaClass.java, и от PythonToJavaClass.class, который является классом Python кода, конвертированным в .class файл, так что makefile создает Java программу с минимумом необходимых перекомпиляций.

Java-Python Extension (JPE)

Альтернативой для Jython является Java-Python Extension (JPE), которое напрямую соединяется с родной реализаций C-Python.

Jython работает полностью под управлением JavaVM, в следствие чего получаем два фундаментальных ограничения: Jython не может быть вызван из CPython, а также родные расширения Python не могут быть доступны из JPython. JPE связывается с библиотеками C Python, так что JPE может быть вызван из CPython, а родные расширения Python могут быть вызваны из Java через JPE.

Если вам необходимо получить доступ к возможностям вашей родной платформы, JPE может быть для этого простейшим решением. Вы можете найти JPE по адресу http://www.arakne.com/jpe.htm.

Выводы

В этой главе намеренно дан более глубокий обзор Jython, чем это требуется при использовании шаблона проектирования интерпретатора. Несомненно, как только вы решите, что вам нужно использовать интерпретатор, и вы не захотите терять время на изобретение своего собственного языка, решение установить Jython будет достаточно простым, и вы сможете начать хотябы с примера GreenHouseController.

Конечно, этот пример очень прост и вам может понадобиться что-то более сложное, часто требуется передавать туда и обратно более интересные данные. Когда я столкнулся со скудной документацией, я почувствовал, что необходимо больше проэкзаменовать Jython.

В процессе обратите внимание, что может быть другой, равный по мощности шаблон проектирования, скрывающийся здесь, который также может быть назван умножением языков. Он основывается на опыте использования каждого языка для решения определенного класса проблем, которые он решает лучше других. При комбинировании языков вы можете решить проблемы быстрее, чем при использовании одного языка. CORBA - это другой путь для связи между языками, и в то же время связи между компьютерами и операционными системами.

Для меня Python и Java представляют очень могущественную комбинацию для программной разработки, Java по своей архитектуре и набору инструментов, а Python своей экстремально быстрой разработкой (обычно предполагается, что в 5-10 раз быстрее, чем на C++ или Java). Python обычно медленнее, однако, даже если вы переписываете часть кода вашей программы для ускорения, начальная быстрая разработка позволит вам быстрее растить систему и обнаруживать и решать критические места. И часто скорость выполнения Python не является проблемой - в таких случаях это большая победа. Некоторые коммерческие продукты уже используют Java и Jython, и поэтому картина с ужасающей производительностью, я думаю, изменится в ближайшем будущем.

Упражнения

  1. Измените GreenHouseLanguage.py так, чтобы он проверял время для событий и запускал эти события в соответствующее время.
  2. Измените GreenHouseLanguage.py так, чтобы он вызывал функции для акций вместо простой печати строки.
  3. Создайте Swing приложение с JTextField (в котором пользователь может вводить команды) и добавьте JTextArea (в котором будут появляться результаты выполнения команд). Соедините объект PythonInterpreter так, чтобы его вывод посылался в JTextArea (который должен скроллироваться). Вы должны найти команду PythonInterpreter, которая перенаправит вывод в поток Java.
  4. Измените GreenHouseLanguage.py, добавив класс главного контроллера (вместо статического массива внутри Event) и предоставьте метод run( ) для каждого подкласса. Каждый run( ) должен создавать и использовать объект из стандартной Java библиотеки во время своего выполнения. Измените GreenHouseController.java на использование нового класса.
  5. Измените результирующий GreenHouseLanguage.py из второго упражнения, чтобы произвести Java классы (добавьте строку документирования @sig для производства корректной сигнатуры Java, и создайте makefile для построения .class файлов). Напишите Java программу, использующую эти классы.