今回は、GolangからPythonを呼び出す練習として、数字のフォーマット処理をPythonで実行し、その結果をGolangで取得して表示するプログラムを実装してみます。
事前知識
GolangからPythonに引数を渡して処理してもらい、その処理結果を受け取るイメージとして、パイプを知っておくとイメージが湧きやすいと思います。
パイプは、ターミナル上で入出力のやり取りをするときに使うアイツ(コイツ→ | )のこと。
今回のパイプのイメージはこんな感じ。
Golang (親プロセス) --> [パイプ] --> Python (子プロセス)
<-- [パイプ] <-- 標準出力 (結果の受け渡し)
今回の処理は、Golangで処理を開始し、その中でPythonを呼び出して一部の処理を実施してもらって、その結果をGolang側で受け取り、後続の処理を続けます。
そのため、Golang側(呼び出す側)が親プロセスとなり、Python側(呼び出される側)が子プロセスとなります。
サンプルコード
乱数を生成して、その乱数が一致したら何回目で一致したかを出力するプログラムを実装してみます。
出力する回数の数字を3桁ごとにカンマ区切りする処理をPythonでやってもらいます。
Golang
package main
import (
"fmt"
"math/rand"
"os/exec"
"strconv"
)
func main() {
// Pythonスクリプトのパス
pythonScriptPath := "sample.py"
cnt := 1
for {
// 乱数を生成
randomNumber := rand.Intn(10000)
randomNumber2 := rand.Intn(10000)
// 一致した場合
if randomNumber == randomNumber2 {
// Pythonスクリプトを呼び出してフォーマット
formattedCnt, err := callPythonFormat(pythonScriptPath, cnt)
if err != nil {
fmt.Println("Error calling Python script:", err)
return
}
// 結果を出力
fmt.Printf("%s 回目で乱数が一致しました。\n", formattedCnt)
return
} else {
cnt += 1
}
}
}
// Pythonスクリプトを呼び出す関数
func callPythonFormat(scriptPath string, number int) (string, error) {
// コマンドを構築
cmd := exec.Command("python3", scriptPath, strconv.Itoa(number))
// 出力を取得
output, err := cmd.Output()
if err != nil {
return "", err
}
// 出力を文字列として返す
newOutput := output[:len(output)-1] // 最後に改行コードがついてるので、除去
return string(newOutput), nil
}
Python
import sys
# 3桁ごとにカンマ区切りする関数
def format_num(num):
formatted_num = f"{num:,}"
return formatted_num
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python sample.py <number>")
sys.exit(1)
# コマンドライン引数を取得して整形
number = int(sys.argv[1])
print(format_num(number))
サンプルコードの説明
-
Golang
- 生成した2つの乱数が一致したら、何回目で一致したかを出力する
- 生成した2つの乱数が一致しなかったら、カウントを回す
- callPythonFormat関数で、整数を3桁ごとにカンマ区切りする処理をするPythonスクリプトを呼び出す
- exec.Command()を使用して、Pythonスクリプトを実行する
- これは、ターミナルでPythonを実行するように引数を見てあげるとわかりやすい
- 整数も文字列として渡してあげる必要があるので、文字列に変換することを忘れずに
- cmd.Output()は、処理がうまくいったら[]byte型で返ってくるので、callPythonFormatの戻り値は、string()で文字列にして返す。なお最後に改行コード(PythonのPrint関数によるもの)がついているので、それを取り除いた文字列を返している
※以下のように、文字列に直して改行コードを除去するのもありです。newOutput := strings.TrimSpace(string(output))
-
Python
- format_num関数は、引数で受けた整数を3桁ごとのカンマ区切りにした文字列に変換して返す関数
- sys.argvは、Pythonスクリプトに渡されたコマンドライン引数を格納したリストになっている
- ターミナルで以下のコマンドを実行した場合:
python3 sample.py 42
- sys.argvの中身:
sys.argv = ['sample.py', '42']
- ターミナルで以下のコマンドを実行した場合:
- sys.exec(status)は、Pythonスクリプトを終了するための命令で、statusが0なら正常終了、statusが1なら異常終了を表す
間違えやすいポイント
「Pythonの print(format_num(number)) の部分を format_num(number) としてもいいのでは??」と思うかもしれませんが、これはNGです。
なぜかというと、事前知識のイメージでもあったように、これは標準出力で処理結果を受け渡ししているからです。
format_num(number) とすると、3桁ごとにカンマ区切りする処理自体は行われているのですが、それを標準出力していないため、Golangはパイプで受け取ることができません。
そのため、print(format_num(number)) とする必要があります。
最後に
forループの中で何度もPythonスクリプトを呼ぶと、オーバーヘッドが生じてしまいますが、if文などで特定の条件のときだけ呼ばれるようにすれば、問題ないと思います。
また、今回は3桁ごとのカンマ区切りをPythonで実行しましたが、Golangにもライブラリで簡単に実行することができます。
参考
【Golang】数値をコンマ区切りで出力する【1000桁単位のカンマ区切りフォーマット出力】:https://qiita.com/KEINOS/items/d8c14f0951ec890ae3ff