Jython使うとJVM上でPythonスクリプトが実行できます。
使う奴いるのか?と思うかもしれませんが、私です。ここにいます。
Jython
本家
http://www.jython.org/
wikipedia
https://ja.wikipedia.org/wiki/Jython
使い方(Javaコード)とハマりどころを書いていきます。
JavaからPythonスクリプト実行
ググって調べると、インストールしてコマンドラインから使う方法が多く出てくる印象ですが、今回、私はJavaのコードから動かします。
まずはmaven pom.xml
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
</dependency>
簡単なPythonスクリプト実行
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スクリプトとの変数のやり取り
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・・・
と思いきや日本人はここでハマると思います。次を見てください。
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ばかり勉強していたので絶望しました)
文字列とJythonとの格闘
Python2ということはunicodeにしないといけなかったわけですね。
ということで、
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で扱ってみる。
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
なんとなく予想ついてた。
書きかえる
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でエラー
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ファイルを作成
# coding:utf-8
def add_numbers(a, b):
return a + b
これを読み込んで実行する
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ファイルが置いてあったらこう書きます。
props.put("python.path", "C:/・・・・.jar/python");
絶対、pythonフォルダに存在するとわかっているならこんな感じで書くこともできます。
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系も勉強しないと。。。