JythonでJavaからPythonを使う。あとハマったこと。

Jython使うとJVM上でPythonスクリプトが実行できます。
使う奴いるのか？と思うかもしれませんが、私です。ここにいます。

# Jython

本家
http://www.jython.org/
wikipedia
https://ja.wikipedia.org/wiki/Jython

使い方（Javaコード）とハマりどころを書いていきます。

# JavaからPythonスクリプト実行
ググって調べると、インストールしてコマンドラインから使う方法が多く出てくる印象ですが、今回、私はJavaのコードから動かします。

まずはmaven pom.xml

```xml:pom.xml
		<dependency>
			<groupId>org.python</groupId>
			<artifactId>jython-standalone</artifactId>
			<version>2.7.0</version>
		</dependency>
```

## 簡単なPythonスクリプト実行
```java

import java.util.Properties;
import org.python.util.PythonInterpreter;

//・・・

	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {

			interp.exec("a = 1 + 2");
			interp.exec("print(a)");
		}
	}
```
結果（コンソール）

```
3
```

`python.console.encoding`が具体的になんなのかはよくわかりませんでしたが、無いとエラーが出るのでググったらこう書けって出てきたので書いてます。

## Pythonスクリプトとの変数のやり取り

```java
import org.python.core.PyInteger;
import org.python.core.PyObject;

//・・・
	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			PyInteger x = new PyInteger(10);
			interp.set("x", x);//10をxに代入
			interp.exec("a = x + 2");

			PyObject a = interp.get("a");//aの結果を取得

			System.out.println(a);
			System.out.println(a.getClass());
		}
	}
```
結果

```
12
class org.python.core.PyInteger
```

PyObjectの実装クラスがいろいろあってこれをsetしたりgetしたりしてJavaのコードとPythonコードでやり取りできます。

int渡したければPyInteger、str渡したければPyString・・・
と思いきや日本人はここでハマると思います。次を見てください。

```java
	PyString s = new PyString("あいうえお");
```
`"あいうえお"`という文字列のPyStringを生成しようとするとこうなります。

```
Exception in thread "main" java.lang.IllegalArgumentException: Cannot create PyString with non-byte value
	at org.python.core.PyString.<init>(PyString.java:64)
	at org.python.core.PyString.<init>(PyString.java:70)
	at ・・・・.main(・・・・)
```

ぐぬぬ。
まず理解すべきなことが、JythonさんはPython2系のサポートです。Python3系のコードは動作しません。
（[Python3ばかり勉強していた](http://qiita.com/ota-meshi/items/3f14dae4447af7cd04b2)ので絶望しました）

## 文字列とJythonとの格闘

Python2ということはunicodeにしないといけなかったわけですね。
ということで、

```java
	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			PyUnicode s = new PyUnicode("あいうえお");
			interp.set("s", s);
			interp.exec("a = s * 10");

			PyObject a = interp.get("a");

			System.out.println(a);
			System.out.println(a.getClass());
		}
	}
```

```
あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお
class org.python.core.PyUnicode
```

できたー。

### Python上はstrで扱いたい
Pythonスクリプト上でencodeしてstrで扱ってみる。

```java
	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			PyUnicode s = new PyUnicode("あいうえお");
			interp.set("s", s);
			interp.exec("s = s.encode('utf-8')");
			interp.exec("a = s * 10");

			PyObject a = interp.get("a");

			System.out.println(a);
			System.out.println(a.getClass());
		}
	}

```

```
ãããããããããããããããããããããããããããããããããããããããããããããããããã
class org.python.core.PyString
```

なんとなく予想ついてた。
書きかえる

```java
	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			PyUnicode s = new PyUnicode("あいうえお");
			interp.set("s", s);
			interp.exec("s = s.encode('utf-8')");
			interp.exec("a = s * 10");
			interp.exec("a = a.decode('utf-8')");

			PyObject a = interp.get("a");

			System.out.println(a);
			System.out.println(a.getClass());
		}
	}
```
```
あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお
class org.python.core.PyUnicode
```

変数を取る前にdecodeしてunicodeにしておけばOKでした。

### upperでエラー

```java
	public static void main(String[] args) {
		Properties props = new Properties();
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			PyUnicode s = new PyUnicode("aあ");
			interp.set("s", s);
			interp.exec("s = s.encode('utf-8')");
			interp.exec("a = s.upper()");
			interp.exec("a = a.decode('utf-8')");

			PyObject a = interp.get("a");

			System.out.println(a);
			System.out.println(a.getClass());
		}
	}
```
結果`Aあ`を期待するところ

```
Exception in thread "main" Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "・・・\repository\org\python\jython-standalone\2.7.0\jython-standalone-2.7.0.jar\Lib\encodings\utf_8.py", line 16, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 3: unexpected code byte
```

Python上でupperすると`new PyString()`されてマルチバイト文字が入っているのでエラーになるようです。たぶん。
ここは注意してコード書く以外に回避方法はなさそうな印象です。str使わずunicode使ったほうがいいですね。
（方法があればご指摘いただきたい）
ただ外部のPythonライブラリで使われたらどうしようもない。かな？。。。



## 外部Pythonライブラリの読み込み（ディレクトリ）

どこかのディレクトリに下記pyファイルを作成

```py:sample.py
# coding:utf-8
def add_numbers(a, b):
	return a + b
```
これを読み込んで実行する

```java
	public static void main(String[] args) {
		Properties props = new Properties();

		props.put("python.path", "【上のsample.pyのあるディレクトリ】");
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			interp.exec("import sample"); //sample.pyをimport
			interp.exec("a = sample.add_numbers(2, 3)");
			interp.exec("print(a)");
		}
	}
```

```
5
```

`python.path`にディレクトリを設定します。
複数ある場合は区切り文字でつなげることで対応できるようです。（Windowsは`;`）
http://www.jython.org/archive/22/userfaq.html#my-modules-can-not-be-found-when-imported-from-an-embedded-application


※2017/7/13追記
pythonのコードから、`sys.path.append`でも外部ディレクトリを読み込める様子です。
（jar内はまだ試していません。）

## 外部Pythonライブラリの読み込み（jar内）

jar内のpyファイルも実行できます。

指定方法は
`【JarのあるPath.jar】\【pyファイルのあるjar内フォルダ名】`と指定します。

jar内でpythonというフォルダにpyファイルが置いてあったらこう書きます。

```java
props.put("python.path", "C:/・・・・.jar/python");
```

絶対、pythonフォルダに存在するとわかっているならこんな感じで書くこともできます。

```java
	public static void main(String[] args) {
		Properties props = new Properties();

		props.put("python.path", getPythonPath());
		props.put("python.console.encoding", "UTF-8");

		PythonInterpreter.initialize(System.getProperties(), props, new String[0]);
		try (PythonInterpreter interp = new PythonInterpreter()) {
			interp.exec("import sample");
			interp.exec("a = sample.add_numbers(2, 3)");
			interp.exec("print(a)");
		}
	}

	private static String getPythonPath() {
		try {
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			URL root = classLoader.getResource("python");

			if ("file".equals(root.getProtocol())) {
				Path path = Paths.get(root.toURI());
				return path.toString();
			} else if ("jar".equals(root.getProtocol())) {
				URI jarFileUri = new URI(root.getPath().replaceFirst("!/.*$", ""));
				Path path = Paths.get(jarFileUri);
				return path.resolve("python").toString();
			}
		} catch (URISyntaxException e) {
			throw new IllegalStateException(e);
		}
		throw new IllegalStateException("pythonディレクトリが見つかりません");
	}
```
これなら、平のpyファイルでも、jarに梱包されたpyファイルも実行できます。
これで開発用とdeploy先用でコード書き換えなくても済みます。


---

最後に
# ハマりどころまとめ

* Jythonで動くPythonスクリプトは2系で動くように記述する。
* Jythonのstrはマルチバイトを受け付けないことが多い。最初だけ受け付けても処理中でエラーになることもある。
* 外部ライブラリの実装が完全Python3系だと動かない。
* 外部ライブラリの実装でstrを多用されていたらエラーになることがある。

まずは2系も勉強しないと。。。