JythonでPythonとJava間での値の受け渡し

JavaでPythonを実行するJythonで、Javaにある値をPythonに渡す方法と、Pythonにある値をJavaに持ってくる方法です。
Pythonの型で値を渡すためのPyObjectの生成方法も紹介します。


# PythonInterpreterでの受け渡し

### JavaからPythonに値を渡す

```java:
	public static void main(String[] args) {
		StringWriter writer = new StringWriter();
		try(PythonInterpreter pyInterp = new PythonInterpreter()) {
			pyInterp.setOut(writer);

			PyInteger num = new PyInteger(5);
			pyInterp.set("num", num);
			pyInterp.exec("print(num)");
			pyInterp.exec("print(type(num))");

			System.out.println(writer);
		}
	}
```

```
5
<type 'int'>
```

Pythonで使う変数名と、インスタンスを表すPyObjectまたはそのサブクラスを、PythonInterpreterに`set`することで変数を定義できます。


### PythonからJavaに値を持ってくる
```java
	public static void main(String[] args) {
		try(PythonInterpreter pyInterp = new PythonInterpreter()) {
			pyInterp.exec("num = 10");

			PyObject pyObj = pyInterp.get("num");
			Object o = pyObj.__tojava__(Integer.class);

			System.out.println(o);
			System.out.println(o.getClass());
		}
	}
```
```
10
class java.lang.Integer
```

`get`メソッドでPyObjectを取得できます。
PyObjectには`__tojava__`メソッドがあり、引数に入れた型への変換に対応していれば、その型へ変換して返します。Object型はどのクラスも対応しているので、`Object.class`を引数に入れればとりあえず変換してくれます。

対応していない場合は`Error`を表すPyObjectのサブクラスが返ってきます。
```java
Object o = pyObj.__tojava__(Date.class);
```
```
Error
class org.python.core.PySingleton
```





# ScriptEngineでの受け渡し
```java
	public static void main(String[] args) throws ScriptException {
		String script = "from decimal import Decimal\n" +
				"y = num + Decimal(10)";
		ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("python");
		SimpleBindings bindings = new SimpleBindings();

		PyInteger num = new PyInteger(5);
		bindings.put("num", num);

		scriptEngine.eval(script, bindings);

		Object y = bindings.get("y");
		System.out.println(y);
		System.out.println(y.getClass());
	}
```
```
15
class java.math.BigDecimal
```

ScriptEngineで実行するときも同様に、SimpleBindingsを通して値を受け渡しできます。
スクリプト実行後のSimpleBindingsには、PyObjectの`__tojava__(Object.class)`で変換した値が格納されています。





# PyObjectの生成

JavaからPythonに値を渡すときに、PyObjectでなくても直接Javaの値を渡すことができます。
```java
	public static void main(String[] args) {
		StringWriter writer = new StringWriter();
		try (PythonInterpreter pyInterp = new PythonInterpreter()) {
			pyInterp.setOut(writer);

			pyInterp.set("num", 0.5);
			pyInterp.set("dt", new Date()); // java.util.Date型を渡す
			pyInterp.exec("print(num)");
			pyInterp.exec("print(type(num))");
			pyInterp.exec("print(dt)");
			pyInterp.exec("print(type(dt))");

			System.out.println(writer);
		}
	}
```
```
0.5
<type 'float'>
Mon Jul 31 01:01:41 JST 2023
<type 'java.util.Date'>
```

しかし、Pythonの型に変換されるのは、数値や文字列などの一部の基本的なJavaの型のみです。サポートされていない型は、Javaの型のままPythonに渡されます。

そのため、Pythonの型として値を渡したい場合は、PyObjectを生成して値を渡す必要があります。


### モジュールのクラスのインスタンスを生成する

数値や文字列のPyObjectはjavaで実装されているため、`new PyInteger`のようにPyObjectを直接生成することが可能でした。
.pyファイルで実装されているモジュールのクラスを利用したい場合は、クラスを表すPyObjetからコンストラクタやファクトリメソッドを実行してインスタンスのPyObjectを生成します。
```java:
	public static void main(String[] args) {
		StringWriter writer = new StringWriter();
		try(PythonInterpreter pyInterp = new PythonInterpreter()) {
			pyInterp.setOut(writer);

			PyObject datetimeModule = __builtin__.__import__("datetime");
			PyObject datetimeClass = datetimeModule.__getattr__("datetime");
			PyObject strptimeMethod = datetimeClass.__getattr__("strptime");
			PyObject dt = strptimeMethod.__call__(new PyObject[]{
					new PyString("2023/07/31 12:34:56"),
					new PyString("%Y/%m/%d %H:%M:%S")
			});

			pyInterp.set("dt", dt);
			pyInterp.exec("print(dt)");
			pyInterp.exec("print(type(dt))");

			System.out.println(writer);
		}
	}
```
```
2023-07-31 12:34:56
<class 'datetime.datetime'>
```

`strptime`メソッドを表すPyObjectに、必要な引数をPyObject[]で渡して`__call__`を呼ぶことで、strptimeを実行しています。これでdatetimeクラスのインスタンスのPyObjectが生成されます。


### Pyクラスから生成する

```java
PyInteger num = Py.newInteger(5);
PyObject decimal = Py.newDecimal("123.456");
```
PyクラスはJythonの内部で使う基本的な機能を提供するユーティリティクラスで、newから始まるメソッドには、インスタンスを表すPyObjectを生成する機能が実装されています。


### PyObjectAdapterで生成する
数値や文字列などの一部のJavaの型が自動でPythonの型に変換されていたのは、PyObjectAdapterで対応するPyObjectを生成していたためです。

今回はサブクラスであるClassAdapterを実装し、Dateクラスからdatetimeクラスへの変換を追加してみます。

```java
	static class DateAdapter extends ClassAdapter {
		DateAdapter() {
			super(Date.class);
		}
		@Override
		public PyObject adapt(Object o) {
			Date date = (Date) o;
			return Py.newDatetime(new Timestamp(date.getTime()));
		}
	}

	public static void main(String[] args) {
		StringWriter writer = new StringWriter();
		try (PythonInterpreter pyInterp = new PythonInterpreter()) {
			pyInterp.setOut(writer);
			
			Py.getAdapter().add(new DateAdapter()); // アダプターを追加する

			pyInterp.set("dt", new Date()); // java.util.Date型を渡す
			pyInterp.exec("print(dt)");
			pyInterp.exec("print(type(dt))");

			System.out.println(writer);
		}
	}
```
```
2023-07-31 01:25:17.395000
<class 'datetime.datetime'>
```
Date型を直接渡しましたが、型が`<type 'java.util.Date'>`ではなく`<class 'datetime.datetime'>`になりました。