前回記事でpythonを使ってWEBのデータを扱えるようになりました。
しかし、CUIではいかんせん扱いにくいので、C#から呼び出せないかといろいろ調べました。
いくつかやり方はあるようですが、pythonnetというnugetパッケージを使ったやり方で試してみたいと思います。
※このやり方、紹介記事があまり見つからなかったので試行錯誤してます。
環境
- VS2022
- .NET6
- WinForm
必要パッケージ
C#側
- pythonnet
python側
- requests
- feedparser
python側の修正
こちらのコードをもうちょっとモジュールっぽく書き換えます。
import requests import feedparser from requests.auth import HTTPBasicAuth class HatenaEntryLoader : def __init__(self, baseurl_, user_, password_) : self.baseurl = baseurl_ self.user = user_ self.password = password_ self.entries = [] def load_entry(self) : ret = self.__load_entry(self.baseurl + "entry") self.__set_entries(ret.entries) next = self.__get_next_data(ret) while next != "" : ret = self.__load_entry(next) self.__set_entries(ret.entries) next = self.__get_next_data(ret) return self.entries def get_all_title(self, entries) : result = [] for e in entries : result.append(e.title) return result def __load_entry(self, url) : response = requests.get(url, auth=HTTPBasicAuth(self.user, self.password)) return feedparser.parse(response.content) def __get_next_data(self, response) : try : return response.feed.links[1].href except : return "" def __set_entries(self, entries) : for entry in entries : self.entries.append(entry) def create_loader(baseurl, user, password): return HatenaEntryLoader(baseurl, user, password)
print()
を使ってたりした部分をなくし、ロジックのみに変更しています。
また、(C#側でのインスタンス生成方法がわからなかったので)インスタンス生成メソッドを公開しています。
C#側の実装
UIの作成
これまでの引数要素のほか、.pyファイルを置いてあるパスへをpythonのシステムに伝える必要があるのでそれを指定するTextBoxも配置します。
pythonnetの初期化
pythonのDLLをpythonnetに登録し、初期化します。
自身の使用しているバージョンに合わせて調整してください。
protected override void OnLoad(EventArgs e) { base.OnLoad(e); Python.Runtime.Runtime.PythonDLL = @"C:\Python311\python311.dll"; Python.Runtime.PythonEngine.Initialize(); }
「読み込み」ボタン押下時の処理
最初にコード全体です。
private void _buttonLoad_Click(object sender, EventArgs e) { using var _ = Python.Runtime.Py.GIL(); var baseurl = _textBoxBaseUrl.Text; var user = _textBoxUser.Text; var password = _maskedTextBoxPass.Text; //環境変数に*pyのおいてあるパスを登録する dynamic sys = Python.Runtime.Py.Import("sys"); sys.path.append(_textBoxPATH.Text); //作ったpythonモジュールをインポートする dynamic p = Python.Runtime.Py.Import(@"HatenaEntryLoader"); //インスタンス生成して処理の実行 dynamic l = p.create_loader(baseurl, user, password); dynamic entries = l.load_entry(); dynamic titles = l.get_all_title(entries); foreach (string item in titles) { textBox1.Text += item + Environment.NewLine; } }
説明しようかと思いましたが、コメントがほぼ説明してますね。
最初の
using var _ = Python.Runtime.Py.GIL();
だけは何してるかわからないですが、おまじないと思っておきましょう。
終了処理
protected override void OnClosed(EventArgs e) { Python.Runtime.PythonEngine.Shutdown(); base.OnClosed(e); }
これを呼び出さないと、フォームを閉じてもプロセスが終了しませんでした。
実行結果
ちゃんと情報取れてUI上に表示できました。
pythonnetを使ってみた感想
商用利用にはまず使えない
WinForm主体の商品をリリースしている場合、まずこの手法は採用されないと思います。
- ユーザー環境にpythonのインストールが要求される
- ユーザー操作によるpythonのバージョン更新等が制御できない
- .py自体を書き換えられるリスク
保守性の低さ
- dynamic使ってる以上、避けられない課題
- 必ずC#層でラッパーを作りそれを介するようにするのはMUST
結構重い
- 当然ですが、特に初期化とかのあたりで気になる程度には待たされました
個人、または社内利用ならあり
pythonの強みを活かす価値はあると思うので、個人利用または社内利用ならありかとは思います。
おしまい
否定的な意見を最後に書いてしまいましたが、テクニックとしてはすごいと思いますし、個人利用の範囲でなら活用機会はいろいろ思いつくので、もうちょっと使っていくとは思います。