こんにちは。
現役エンジニアの”はやぶさ”@Cpp_Learningです。
以前、標準入出力を使って手軽にPythonとC/C++を連携する方法の記事を書きました。
(本記事の理解が深まるので、この記事を読んでから、続きを読んでほしいなぁ)
この記事の【応用】として、C++とPythonを連携してC++コードのテストを実践しました。
備忘録も兼ねて本記事を書きます。
Contents
やりたいこと(課題)
Pythonでプログラミングをしていると、処理速度を向上させたくて、処理の一部をC++に置き換えることがあります。
いくつか方法はありますが、”Python C API”を使ったりします(下記の記事参照)。
一方、C++でプログラミングをしていると、グラフの描画(matplotlib)や表の操作(pandas)などのPythonライブラリを使いたいときがあります。
例えば、こんなとき…
【C++でPythonライブラリが使いたいとき】
- 自作関数をテストするとき、出力結果を可視化したい
- 報告書用のグラフや表を手軽に作成したい
C++で出力結果をCSVファイルに保存するコードを作成して、エクセルで結果を可視化することもありますが…
と思い、C++とPythonを連携してテストする方法を検討しました。
Python-ShellExecutionとは
冒頭で手軽にPythonとC/C++を連携する方法の記事を紹介しました。
この記事では、”subprocess”を使ってC++の実行ファイルをPythonから呼び出していました。
今回は、”subprocess”をより使いやすくしたライブラリ”ShellExecution”を見つけたので、有り難く使わせて頂きます。
”ShellExecution”開発者様のブログはこちら↓
Python-ShellExecutionの使い方 -基本編-
本題のテストを実践する前に、”ShellExecution”の一番シンプルな使い方を紹介します。
例えば、C++で作成した足し算ソフト(実行ファイル:a.out)があったとします。
↑の”2”と”3″はユーザーがキーボードで入力した値ですが、この入力をPythonにやらせます。
作成したPythonコード(main.py)が以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import ShellExecution as SE import sys def main(): a = 2.2 b = 3.3 print(" ===== exe ===== ") cmd = "./a.out " + str(a) + " " + str(b) result, out = SE.exe(cmd) # call C/C++ executable file if result != 0: print("error") sys.exit() else: ans = float(out) + 1 print(ans) if __name__ == '__main__': main() |
main.pyを実行すると、”a=2.2”と”b=3.3″が入力され、C++の演算結果”a+b=5.5”に対し、+1した”6.5”が出力(表示)されます。
”ShellExecution”を使うことで、Pythonから手軽に外部ファイルを実行でき、かつエラーチェックまでしてくれるます。
なので、今回のようにC++で作成した外部ファイル(a.out)の演算結果をPythonから利用することで、手軽にC++とPythonの連携を実現できます。
- ”ShellExecution”を使えば、Pythonから手軽に外部ファイルを実行できます
- 外部ファイルをC++など自分の好きな言語で作成すれば、C++とPythonの連携を実現できます
Python-ShellExecutionの使い方 -応用編-
本題のC++テスト用のPythonコードを作成します。
テスト対象と課題
任意の値”x”を入力すると、演算結果”y”を出力するC++の自作関数があったとします。この自作関数がテスト対象です。
例えば、ある値を入力したとき、”36”または”144”が出力されたらNG※とします。
(※自作関数にバグあり、あるいはシステム上、都合の悪い入力値を検出できた)
入力値が少ない場合は、手動でキーボードから値”x”を入力するか、【基本編】のコード(main.py)を使えば良いのですが…
テストしたい入力値が大量にある場合や、最初から自作関数に連続入力するシステムを想定している場合、テスト時の入力は自動化したくなります。
このような課題を”ShellExecution”を使用したPythonコードで解決します!
テスト対象のC++コード
今回は、y=x^2の演算を行う自作関数”square(x)”をテスト対象とします。
最初に”square(x)”を繰り返し呼び出す処理をC++で作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> // Pure C/C++ function (Test target) int square(int x) { return x * x; } int main(int argc, char* argv[]) { int start, end, ans; if (argc >= 3) { start = std::stod(argv[1]); end = std::stod(argv[2]); } for(int i = start; i <= end; i++){ ans = square(i); std::cout << ans << std::endl; } return 0; } |
このC++ファイルを”square.cpp”と呼ぶことにします。
「最初の入力値」と「最後の入力値」をユーザーが指定できるようにしました。
以下が実行時のイメージです。
C++コード(square.cpp)の動作確認
作成したC++コード(square.cpp)を以下のコマンドでコンパイルします。
g++ square.cpp -o square
これで”square”という実行ファイルが生成されます。
以下のコマンドで”2~15”の二乗を算出します。
./square 2 15
演算結果が出力(表示)されれば動作確認完了です。
一連の流れを実施したときのターミナル画面は以下の通りです。
【基本編】同様にユーザーが入力していた部分をPythonにやらせます。
以下がイメージです。
C++テスト用のPythonコード
”ShellExecution”を使用する最大のメリットはfor文などの繰り返し処理中の標準出力も扱うことができることです。
コードを見た方が分かりやすいですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import ShellExecution as SE import sys import matplotlib.pyplot as plt def main(): start = 2 end = 15 ans_list = [] print(" ===== realtime_exe ===== ") cmd = "./square " + str(start) + " " + str(end) for out in SE.realtime_exe(cmd): ans = int(out) print(ans) if(ans == 36 or ans == 144): # ans = ans * 2 ans = -1 print("Get Target Parameter =", ans) ans_list.append([ans]) print("ans_list =", ans_list) plt.plot(ans_list, linewidth = 1.0, marker = 'o') plt.title("Get Err Parameter") plt.xlabel("input") plt.ylabel("output") plt.grid(True) plt.show() if __name__ == '__main__': main() |
このPythonファイルを”square.py”と呼ぶことにします。
【基本編】では、”exe”を使って自作コマンド(cmd)を実行しましたが、【応用編】では“realtime_exe”を使いました。
“realtime_exe”を使うことで、C++の標準出力を1つずつPythonが受け取ることができます。
もし、“realtime_exe”ではなく”exe”を使っていたら、最後の標準出力(225)しかPythonは受け取ることができません。
C++コードのテストをPythonで行う
作成したPythonコード(square.py)を以下のコマンドで実行します。
python square.py
一連の流れを実施したときのターミナル画面は以下の通りです。
”36”または”144”が出力されたらNGでしたが、ちゃんと”36”と”144”を検出できています。
これだけだと、C++のみでテストしても良い気がしますが、Pythonを使うことで出力結果のリスト(ans_list)を簡単に生成することができました。
このリスト(ans_list)に対し、グラフの描画(matplotlib)や表の操作(pandas)が簡単に実現できます。
今回は、matplotlibを使ってテスト結果を可視化してみました。
一目でNGが分かるように”36”と”144”を出力した場合は、”-1”をプロットするようにしました。
※0番目があることに注意!あと”2~15”を入力しています
Pythonを使ってテスト結果を手軽に可視化することで、NG結果の見落としを予防できそうです!
C++の演算結果(標準出力)をPythonで受け取り、”NG結果を強調”するような工夫をして、matplotlibなどでテスト結果を可視化すれば、良いことあるかも!
まとめ
Pythonから手軽に外部ファイルを実行できるライブラリ”ShellExecution”を使って以下のことを説明しました。
【基本編】
C++とPythonを連携する方法(外部ファイルをC++で作成)
【応用編】
C++コードのテストをPythonと連携して楽する方法(ループ処理に対応)
Pythonと連携してテストを実施するメリットは以下の通りです。
- C++コードをあまり汚さずにテストを実施できる(テスト用のコードをC++で書かなくて済む)
- グラフの描画(matplotlib)や表の操作(pandas)が簡単に実現できる強力なPythonライブラリと連携できる
本記事では、matplotlibを使ってテスト結果の可視化を行い、NG結果を一目で確認できるように工夫しました。