Java → Python 実行結果の文字コード

以前、Java から Python 実行した時の結果をPython標準出力で Java が受信する方法を投稿したが、
Javaからプロセス起動で実行するPython と文字列の受け渡し - Oboe吹きプログラマの黙示録

Python 標準出力→Java受け取り - Oboe吹きプログラマの黙示録
このような、Base64 コードに変換して仲介するようなことをしなくても良いことに気がついた。

Python から、Java へ正常時の結果出力(標準出力)は、JSONで出力して
Java側がJSONパースするのが綺麗であろう。
Python 実行中でエラー発生、エラー出力:標準エラー出力の方は、特殊なので後で記載)
Pythonスクリプトが以下のような断片コードを実行しているとする。
Pythonの断片コード

import json

#  class の __init__で記述するもの
self.mydict = dict()
# メソッドで記述するもの
self.mydict['A'] = 'A123'
self.mydict['B'] = 24
self.mydict['C'] = '漢字:氏名'
# 
result = json.dumps(self.mydict)
print(result)

以下を標準出力する。

{"A": "A123", "B": 24, "C": "\u6f22\u5b57\uff1a\u6c0f\u540d"}

受信するJava側は、Jackson または、Google gson で読み取れば、

{"A": "A123", "B": 24, "C": "漢字:氏名"}

として読み込める。

自分が作った ScriptExecutor
https://github.com/yipuran/yipuran-core/wiki/Script_exec#orgyipuranutilprocessscriptexecutor
で、以下のように、コードを書いて確認できる。

Jackson 使用の場合、先日公開した以下を使って、、
https://github.com/yipuran/yipuran-jack/wiki

StringBuilder sb = new StringBuilder();
int sts = ScriptExecutor.run(()->"python c:/work/forJava/resmain.py"
, t->{
    sb.append(t);
}, (t, e)->{
    // エラー捕捉
    pythonErrorTrace(t).forEach(s->{
       System.out.println(s);
    });
});
String jsonstr = sb.toString();
// Jackson JsonNode を ObjectMapper readTree でJsonNode を求めて解析する処理
JsonNodeParse jp = new JsonNodeParse();
jp.stream(jsonstr).forEach(e->{
   System.out.println(e.getKey() + " --> " + e.getValue() );
});

Python用 エラー捕捉→ Stream<String>

public Stream<String> pythonErrorTrace(String error) {
   String estr = error.replaceAll("\r", "").replaceAll("\n", "");
   estr = estr.substring(2, estr.length()-2);
   String[] ary = estr.split("', '");
   return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>(){
      int x = -1;
      @Override
      public boolean hasNext(){
         return x < ary.length-1;
      }
      @Override
      public String next(){
         x++;
         return ary[x].replaceFirst("\\\\n$", "");
      }
   }, Spliterator.ORDERED), false);
}

Google gson であれば、以前作って公開した
https://github.com/yipuran/yipuran-gsonhelper/blob/master/src/main/java/org/yipuran/gsonhelper/util/JsonEntryParse.java
を使用すれば、JSONの解析は、

String jsonstr = sb.toString();
JsonEntryParse jp = new JsonEntryParse();
jp.read(jsonstr, (k, v)->{
   System.out.println(k + " --> "+ v);
});

このように確認できる。

問題は、Python処理内でエラー発生した時に、
・エラーメッセージをJavaで受信した時に文字化けしないこと。
Python エラースタックトレースを中途半端ではなく最後まで取得すること
であった。

Python スクリプト

     raise RuntimeWarning("警告エラー")

を発生するように任意にコーディングします。

スタックトレースをエラー発生まで採取するように、Python標準の traceback モジュールを import して
format_exception スタックトレース採取して、print オプション file=sys.stderr で
スタックトレース標準エラー出力します。

if __name__ == '__main__':
    try:
        main = Main()
        main.exec()
    except Exception as e:
        (etype, evalue, etb) = sys.exc_info()
        print(traceback.format_exception(etype, evalue, etb), file=sys.stderr)

このままでは、Java 側、ScriptExecutor#run() のエラー捕捉の BiConsumer でダンプすると、

Traceback (most recent call last):
  File "c:\work\forJava\resmain.py", line 21, in <module>\n    main.exec()
  File "c:\work\forJava\resmain.py", line 13, in exec\n    self.stool.func()
  File "c:\work\forJava\tools\jpress.py", line 14, in func\n    raise RuntimeWarning("�x���G���[")
RuntimeWarning: �x���G���[

と、文字化けしてしまいます。

標準エラー出力UTF-8で出力するように、Python側で最初に宣言します。

import sys

sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

すると、Java 側、ScriptExecutor#run() のエラー捕捉の BiConsumer でのダンプも
結果は以下のとおりになる

Traceback (most recent call last):
  File "c:\work\forJava\resmain.py", line 21, in <module>\n    main.exec()
  File "c:\work\forJava\resmain.py", line 13, in exec\n    self.stool.func()
  File "c:\work\forJava\tools\jpress.py", line 14, in func\n    raise RuntimeWarning("警告エラー")
RuntimeWarning: 警告エラー

標準出力は、同様に sys.stdout を以下のように設定していても

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

Python が出力する全角文字は、 \uXXXX の書式で出力されるので、

{"A": "A123", "B": 24, "C": "\u6f22\u5b57\uff1a\u6c0f\u540d"}

Java側は、JSON として String値を読み込むロジックであれば、sys.stdout の設定は関係ない。