[C#] Python.Netでvenvを使う際に公式ドキュメントどおりにコードを書いてもアプリが落ちてしまう事象についての備忘録

- C#でPython.NETを使いPythonのモジュールを呼び出すアプリケーションを作っている最中に発生した不具合に関する備忘録です
- Python.NETからpythonのvenvの環境を使用しようとしたときにアプリケーションがクラッシュする事象の回避策を調べた結果です
- 次のドキュメントと実機の情報を基にして、OpenAI(gpt-4o)をつかって生成した文章を一部手直したものです。OpenAIからの応答に一部手を加えましたが、OopenAIに与えるプロンプトと背景情報を改善すれば、手直しすることなく目的のドキュメントが生成できたのかもしれません。

   - 公式ドキュメント：https://github.com/pythonnet/pythonnet/wiki/Using-Python.NET-with-Virtual-Environments
   - GitHubのIssue 1478：https://github.com/pythonnet/pythonnet/issues/1478 
   - GitHubのIssue 1478の中で、回避策として用いた方法：https://github.com/pythonnet/pythonnet/issues/1478#issuecomment-897933730

以下が発生した事象と回避策をまとめた結果です。

### 発生した事象と回避策について
#### これは誰向けのコンテンツ？
このチュートリアルは、Python.NETを使用している開発者やエンジニアを対象としています。特に、.NET環境でPythonの仮想環境を利用しようとしている人々に向けています。

#### 目的
この文書の目的は、Python.NETを使用して仮想環境を正しく初期化するための手順と回避策を提供することです。
特に、`PythonEngine.Initialize()`メソッドの呼び出し時に発生する問題を回避する方法を示すことを目的としています。

#### いつ？どこで発生？
この問題は、Python 3.11.0および.NET 8.0の環境で発生しました。

#### 経緯
この問題は、.NET 8.0環境でPython.NET 3.1.0-preview2024-06-03を使用し、Python 3.11.0の仮想環境（venv）を利用する際に発生しました。
[公式ドキュメント](https://github.com/pythonnet/pythonnet/wiki/Using-Python.NET-with-Virtual-Environments)に従ってコードを記述したにもかかわらず、`PythonEngine.Initialize()`メソッドの呼び出し時にアプリケーションが予期せず終了してしまう現象が発生しました。
この問題は、エラーメッセージが出力されないため原因の特定が困難でした。

#### 原因
問題の原因は、PythonnetがPythonの仮想環境を正しく認識できないことにあります。具体的には、sys.executableの値がPythonインタープリタではなく、埋め込み.NETプログラムの実行ファイルを指しているため、Pythonが正しい環境設定をロードできないことが挙げられます。

#### 事象
Python.Netでvenvを使用するために公式ドキュメントに従い次のようなコードを記述しました。しかし、PythonEngine.Initialize()でアプリケーションがクラッシュしました。
1. **環境変数の設定**：
   - `PATH`: 仮想環境のパスを含める
   - `PYTHONHOME`: 仮想環境のルートパスを設定
   - `PYTHONPATH`: `Lib`と`Lib\site-packages`のサブディレクトリを含める

2. **PythonEngineの設定**：
   - `PythonEngine.PythonHome`: 仮想環境のパスを設定
   - `PythonEngine.PythonPath`: 環境変数から取得した`PYTHONPATH`を設定

3. **サンプルコード**：
```cs
 var pathToVirtualEnv = @"path\to\env";

// be sure not to overwrite your existing "PATH" environmental variable.
var path = Environment.GetEnvironmentVariable("PATH").TrimEnd(';');
path = string.IsNullOrEmpty(path) ? pathToVirtualEnv : path + ";" + pathToVirtualEnv;
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PATH", pathToVirtualEnv, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONHOME", pathToVirtualEnv, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONPATH", $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib", EnvironmentVariableTarget.Process);

PythonEngine.PythonHome = pathToVirtualEnv;
PythonEngine.PythonPath = Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process);

PythonEngine.Initialize()
```

### 回避策の詳細
- **環境変数の設定を見直す**：
  - `PythonHome`と`PythonPath`をデフォルトのものに戻し、`PythonEngine.Initialize()`呼び出し後に`sys.path`を設定する。
  - 具体的には、`PythonEngine.Initialize()`後にPythonの`sys`モジュールをインポートし、`sys.path`に仮想環境の`site-packages`ディレクトリを追加する。

- **サンプルコード**
```cs
                // venvを使用する場合の設定
                // 公式ドキュメントの設定ではPythonEngine.Initialize()時にクラッシュするため、
                // 以下を参考にして設定を行う
                // https://github.com/pythonnet/pythonnet/issues/1478#issuecomment-897933730

                if (!string.IsNullOrEmpty(pathToVirtualEnv)) {
                    // PythonEngineにアクセスするためのダミー処理
                    string version = PythonEngine.Version;
                    LogWrapper.Info($"Python Version: {version}");
                    // 実行中の Python のユーザー site-packages へのパスを無効にする
                    PythonEngine.SetNoSiteFlag();
                }

                PythonEngine.Initialize();
                PythonEngine.BeginAllowThreads();

                if (!string.IsNullOrEmpty(pathToVirtualEnv)) {

                    // sys.prefix、sys.exec_prefixを venvのパスに変更

                    using (Py.GIL()) {
                        // fix the prefixes to point to our venv
                        // (This is for Windows, there may be some difference with sys.exec_prefix on other platforms)
                        dynamic sys = Py.Import("sys");
                        sys.prefix = pathToVirtualEnv;
                        sys.exec_prefix = pathToVirtualEnv;

                        dynamic site = Py.Import("site");
                        // This has to be overwritten because site module may already have 
                        // been loaded by the interpreter (but not run yet)
                        site.PREFIXES = new List<PyObject> { sys.prefix, sys.exec_prefix };
                        // Run site path modification with tweaked prefixes
                        site.main();

                    }
                }
```

### 参考情報
- **関連情報**：
  - この問題に関連するGitHubのIssueやStackOverflowの投稿が存在します。これらの情報を参考にすることで、より詳細な解決策や同様の問題に直面した他の開発者の経験を知ることができます。
  - 例：GitHub Issue #1348、#463、#259、#1444、#1478など。

この情報を基に、問題の解決に役立つ手順や回避策を実施してください。