【C#】C#からPythonコードを実行する(Pythonnet)

# やりたいこと
Pythonでつくった機械学習アルゴリズムの学習作業や経過観察をC#(WPF)から行いたい。

# 環境
* VisualStudio2015
* Python 3.6.5

# 準備
PythonnetのDLLが必要になります。[こちら](https://github.com/pythonnet/pythonnet)からPythonnetのソースコードをクローンし、ビルドします。注意点は、使用するPythonのバージョンに合わせて条件付きコンパイルのシンボルを指定しないといけないこと。

本環境では以下の感じ。
![スクリーンショット 2022-05-02 211355.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610751/64ebbafb-bcde-bb43-f4db-fb0d8bd6a1c4.jpeg)

無事ビルドできたら、生成されたPython.Runtime.dllをC#プロジェクトの参照に追加します。
![スクリーンショット 2022-05-02 212915.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610751/c09b746b-26ee-6836-edb1-aa89be402e86.jpeg)

以上で段取り完了。

# コード
Python側のコードです。コンストラクタに指定された2つの数の加算を返します。
```python:PythonCode.py
class Calculator:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self):
        self.y = self.x + self.y
        return self.y
```

C#コードです。
```cs:PythonnetTest.cs
namespace PythonnetTest
{
    class Program
    {
        /// <summary>
        /// プロセスの環境変数PATHに、指定されたディレクトリを追加する(パスを通す)。
        /// </summary>
        /// <param name="paths">PATHに追加するディレクトリ。</param>
        public static void AddEnvPath(params string[] paths)
        {
            var envPaths = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator).ToList();
            foreach (var path in paths)
            {
                if (path.Length > 0 && !envPaths.Contains(path))
                {
                    envPaths.Insert(0, path);
                }
            }
            Environment.SetEnvironmentVariable("PATH", string.Join(Path.PathSeparator.ToString(), envPaths), EnvironmentVariableTarget.Process);
        }

        /// <summary>
        /// プログラムのエントリポイント。
        /// </summary>
        /// <param name="args">コマンドライン引数。</param>
        static void Main(string[] args)
        {
            // *-------------------------------------------------------*
            // * python環境の設定
            // *-------------------------------------------------------*

            // python環境にパスを通す
            // TODO: 環境に合わせてパスを直すこと
            var PYTHON_HOME = Environment.ExpandEnvironmentVariables(@"C:\ProgramData\Anaconda3");

            // pythonnetが、python本体のDLLおよび依存DLLを見つけられるようにする
            AddEnvPath(
              PYTHON_HOME,
              Path.Combine(PYTHON_HOME, @"Library\bin")
            );

            // python環境に、PYTHON_HOME(標準pythonライブラリの場所)を設定
            PythonEngine.PythonHome = PYTHON_HOME;

            // python環境に、PYTHON_PATH(モジュールファイルのデフォルトの検索パス)を設定
            PythonEngine.PythonPath = string.Join(
              Path.PathSeparator.ToString(),
              new string[] {
                  PythonEngine.PythonPath,// 元の設定を残す
                  Path.Combine(PYTHON_HOME, @"Lib\site-packages"), //pipで入れたパッケージはここに入る
                  Path.Combine(@"C:\foo\bar\my_packages"), //自分で作った(動かしたい)pythonプログラムの置き場所も追加                  
              }
            );

            // 初期化 (明示的に呼ばなくても内部で自動実行されるようだが、一応呼ぶ)
            PythonEngine.Initialize();

            // *-------------------------------------------------------*
            // * pythonコードの実行
            // *-------------------------------------------------------*
            // Global Interpreter Lockを取得
            using (Py.GIL())
            {
                // モジュールの探索パスを表示 (pythonコードを直接指定して実行)
                PythonEngine.RunSimpleString(@"
                                            import sys
                                            import pprint
                                            print('module path =')
                                            pprint.pprint(sys.path)
                                            ");

                // コードの呼び出し
                dynamic myMath = Py.Import("PythonCode"); // "from my_awesome_lib import my_math"
                dynamic calculator = myMath.Calculator(5, 7); // クラスのインスタンスを生成
                Console.WriteLine($"5 + 7 = {calculator.add()}"); // クラスのメソッド呼び出し
            }

            // python環境を破棄
            PythonEngine.Shutdown();

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

```

### 結果
![kekka.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610751/5fadaedd-a4eb-2fab-f890-b9636ef41caa.jpeg)
無事、正しい結果を返してもらえました:relaxed:
環境の指定や、Pythonコードの保存パスなどを間違えなければ、意外とすんなり動きました。
今後は機械学習でのUI構築に役立てたいと思います。