python-shell備忘録

# 初めに
NodejsからPythonを実行えきるライブラリ，python-shellを使い，詰まった点とその解消法を紹介します．
## インストール
```
npm install --save-dev python-shell
```
## 基本・仕様
基本的な使い方はこちらを参考に．
公式ドキュメントは[こちら](https://www.npmjs.com/package/python-shell)．
[Python-shellの使い方 - Qiita](https://qiita.com/NT1123/items/09aed7b23388190cba23)
[Pythonのprint出力をNode.jsが認識できない問題について - Qiita](https://qiita.com/7315_t/items/aa435464b5b42df90822)

```javascript:main.js
// PythonShellの宣言
let options ={
    "mode":"text",
    pythonOptions:['-u'],
    // args:[dir_DebugSave]
}
pyshell = new PythonShell("./test_python.py",options)
// Pythonからメッセージの受け取り
pyshell.on("message",(msg)=>{
    console.log("recieve:")
    console.log(msg)
    result+=msg
})
// Pythonからエラーの受け取り
pyshell.on('error',(err)=>{
    console.log('ERROR:')
    console.err(err)
})
// Pythonへメッセージの送信
// .endの後ろに付けるとエラー．
pyshell.send("hello world");
// Pythonの終了
pyshell.end((err,code,signal)=>{
    if (err) {
        console.log(err)
    }
    else {
        console.log('The exit code was: ' + code);
        console.log('The exit signal was: ' + signal);
        console.log('finished');
    }
})
```
```python:thread.py
import sys
# コマンドライン引数を受け取り
args = sys.argv
# Nodejs からのメッセージを受け取り
var1 = input()
# Nodejsへメッセージを送信
print(var1)
```
# データの送受信
NodejsとPython間ではPythonの標準入出力を利用してデータを送受信します．
テキスト形式のデータしかやり取りできないので，JSONはJSON.stringfyで文字列に，画像はBase64にエンコードする必要があります．
## NodejsからPythonへ送信

|   \    |javascript |python |備考|
|--------|-----|-----|-----|
|コマンドライン引数|`args[]`|`sys.argv`|PythonShell開始時．`args[0]`にはNodeJSで指定したパスが入る．|
|標準入出力|`pyshell.send()`|`input()` or `sys.stdin.readline()`|

## PythonからNodejsへ送信
|\    |python |javascript |備考|
|--------|-----------|---------|----|
|標準入出力|`print()`|`pyshell.on()` |'message'で標準，'error'でエラー出力

## 日本語の受け渡し
日本語を含む文字列をそのまま送受信すると，JavascriptとPyhonの文字コードの違いによって文字化けします．
日本語をURIに変換することで文字化けを防ぐことができます．
### NodeJSからPythonへ
```javascript:sample.js
// 日本語をencodeURI
var jsondata={
    str:"hello",
    str2:encodeURI("こんにちは")
}
pyshell.send(JSON.stringify(jsondata))
```
```python:sample.py
import urllib.parse
import json
# URIからデコード
var1 = input()
val_json=json.loads(var1)
val_json["str2"]=urllib.parse.unquote(val_json["str2"])
```
### PythonからNodeJSへ
```python:sample.py
import urllib.parse
import json
# URIへエンコード
jsondata={
    "str1":"hello",
    "str2":urllib.parse.quote("こんにちは ")
}
```
```javascript:sample.js
// URIからエンコード
pyshell.on("message",(msg)=>{
    var req_json=JSON.parse(msg)
    req_json['str2']=decodeURI(req_json['str2'])
})
```


# 情報の探し方
検索するとき，python-shellのみだとpython shellと解釈され，~~python-shellの情報源が少ないのもあり~~Pythonのシェルの使い方が上位にヒットします．
「"」ダブルクォーテーションでキーワードを囲う，「python-shell nodejs」等キーワードを追加するとこのライブラリについての情報が見つけやすくなります．


# Python側の実行ディレクトリ
Python-shell経由Pythonのスクリプトを実行するとき，python側のディレクトリは**Electronが実行するディレクトリとなります．** pythonのみで実行すると普通に実行できても，Python-shell実行時にファイル読み込み・書き出しができない場合，これが原因である可能性が高いです．
例えば以下のディレクトリ構成で`test.py`をPython-shellで実行すると，
ディレクトリ：
- app/
  ├src/
　└ main.js
├python/
　├test.py
　└setting.txt
```python:test.py
with open("./setting.txt") as f:
    print(f.read())
```
python-shellを通じて実行すると，pythonのスクリプトはディレクトリ「app/src/」で実行している扱いとなります．
そのため，スクリプトの「"./setting.txt"」は「app/src/setting.txt」を参照するため，FileNotFoundとなります（`os.getcwd()`によって取得したパスもNodejsの実行ディレクトリになる）．
Pythonでファイルを読み書きする際，絶対パスを指定することをおすすめします．
## Pythonの正しいディレクトリの取り方
Pythonのコマンドライン引数は，先頭にpythonスクリプトのパスが渡されます．python-shellの場合も同様に，PythonShellで指定したパスが渡されます．
pythonスクリプトを絶対パスで指定することで，コマンドライン引数を通じて正しいディレクトリを取得することができます．
```javascript:main.js
// PythonShellの宣言
let options ={
    "mode":"text",
    pythonOptions:['-u'],
    // args:[dir_DebugSave]
}
pyshell = new PythonShell("./app/python/test.py",options)
```
```python:test.py
import sys
import os
# コマンドライン引数を受け取り
args = sys.argv
# ファイルパスからディレクトリの取得
dir=os.path.dirname(args[0])
```
「Pythonスクリプトの存在するディレクトリ」はnodejs側から指定する必要があります．
# Pythonのデバッグ
python-shellを通じたPythonを実行する場合，print()による出力はNodeJSへ結果を返す扱いとなるので，デバッグにはあまり使用できない．
Python側ではprintの代わりにloggerを使用すると，printと同じ感覚でログをファイル出力できます．
loggerの使い方については[こちらを参照．](https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19)
```python:test.py
import logging
import logging.handlers
logger = logging.getLogger(__name__)
def setLogger(dirPath):
    """Loggerのファイルハンドラーをセットする。

    Args:
        dirPath (str): ファイルを保存するディレクトリ。Nodejsから渡される。
    """
    fh = logging.handlers.RotatingFileHandler(
        dirPath+'pythonlog'+__name__+'.log',
        maxBytes=1024*1000,
        backupCount=2,
        encoding="utf-8"
    )
    LOG_LEVEL=logging.DEBUG
    fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s \n %(message)s')
    fh.setFormatter(fh_formatter)
    fh.setLevel(LOG_LEVEL)
    logging.basicConfig(level=LOG_LEVEL,handlers=[fh])

logger.setLevel(logging.DEBUG)
```
loggerをファイルのみに出力できる．
Nodejsのデバッグ中・ビルド後に関わらず，Pythonのデバッグ情報をファイルに出力できる．

# 参考にしたサイト一覧
- [python-shell - npm](https://www.npmjs.com/package/python-shell) (公式サイト)
- [Python-shellの使い方 - Qiita](https://qiita.com/NT1123/items/09aed7b23388190cba23)
- [Pythonのprint出力をNode.jsが認識できない問題について - Qiita](https://qiita.com/7315_t/items/aa435464b5b42df90822)
- [Pythonでprintを卒業してログ出力をいい感じにする - Qiita](https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19)
- [PythonでURLのクエリ文字列（パラメータ）を取得・作成・変更 | note.nkmk.me](https://note.nkmk.me/python-urllib-parse-query-string/)
- [decodeURI() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/decodeURI)