3
4

More than 1 year has passed since last update.

python-shell備忘録

Last updated at Posted at 2022-12-31

初めに

NodejsからPythonを実行えきるライブラリ,python-shellを使い,詰まった点とその解消法を紹介します.

インストール

npm install --save-dev python-shell

基本・仕様

基本的な使い方はこちらを参考に.
公式ドキュメントはこちら
Python-shellの使い方 - Qiita
Pythonのprint出力をNode.jsが認識できない問題について - Qiita

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');
    }
})
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へ

sample.js
// 日本語をencodeURI
var jsondata={
    str:"hello",
    str2:encodeURI("こんにちは")
}
pyshell.send(JSON.stringify(jsondata))
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へ

sample.py
import urllib.parse
import json
# URIへエンコード
jsondata={
    "str1":"hello",
    "str2":urllib.parse.quote("こんにちは ")
}
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
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スクリプトを絶対パスで指定することで,コマンドライン引数を通じて正しいディレクトリを取得することができます.

main.js
// PythonShellの宣言
let options ={
    "mode":"text",
    pythonOptions:['-u'],
    // args:[dir_DebugSave]
}
pyshell = new PythonShell("./app/python/test.py",options)
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の使い方についてはこちらを参照.

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のデバッグ情報をファイルに出力できる.

参考にしたサイト一覧

3
4
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
3
4