今からでも間に合う

技術を学ぶのは今からでも遅くない

python入門~C#からpythonを呼び出す~

前回記事で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の強みを活かす価値はあると思うので、個人利用または社内利用ならありかとは思います。

おしまい

否定的な意見を最後に書いてしまいましたが、テクニックとしてはすごいと思いますし、個人利用の範囲でなら活用機会はいろいろ思いつくので、もうちょっと使っていくとは思います。

プライバシーポリシー


d払いポイントGETモール