Хотя это напрямую не относится к начальной проблеме этой главы (создание интерпретаторов), 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 | Java-Python Extension (JPE) | → |