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


Предыдущий пример только создавал и запускал интерпретатор при помощи использования внешних скриптов. В оставшейся части главы мы взглянем на более изощренные способы взаимодействия с 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.