LoginSignup
58
53

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-27

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

Jython

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

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

JavaからPythonスクリプト実行

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

まずはmaven pom.xml

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ファイルを作成

sample.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系も勉強しないと。。。

58
53
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
53