前回はパッケージのインストールに失敗しました。

謝罪

まずはじめに。
IronPython の使用を諦めることにしました。
理由はモジュールのインストールができなかったことです。
一番シンプルだと思っていた simplejson すらまともにできませんでした。
正確には、インストールはできましたが、いざ import simplejson すると、動かない。
他にも、

  • CPythonと同じ方法でC言語ベースのライブラリと相互運用ができない
  • 2017/03/11時点で、Issueの数が700越え。あまりにも利用者の声に応える余裕がない。

結局、ネット上で調べると、動かないという情報ばかり、でこれ以上貴重な時間を費やすことはできない、という判断に至りました。
4日以上使いましたが、本当に無駄骨に終わりました。

Introduction

なので、方針を変えます。
「.NETからPythonを使う」 という目的を達するなら、思いつく限り、

  • Pythonをコマンドプロンプトで呼び出し、標準出力で結果を受け取り解析
  • Pythonをコマンドプロンプトで呼び出し、ファイルで結果を受け取り解析
  • Pythonで動作しているWebサービスのAPIを叩く

あたりでしょうか。
1,2番目は面倒ですし、なによりださいので、3番目を利用します。
Webサービスなら、WCFによって簡単に.NETから呼び出せます。
というのも、Pythonをコマンドプロンプトで呼ぶと、呼び出しのコストが大きいと思います。
試してみた限り、Pythonの起動は遅いです。おそらく必要なモジュールなどを読み込んでいるからだと思うのですが、無視できない間隔で待たされます。
なので、起動コストを無視するために、常に起動しっぱなしにして、必要なときに必要な演算結果を受け取る形式がベスト。

ですので、今回はPythonでシンプルなRESTfulサービスを作成し、C#アプリからアクセスします。
そのために軽量なWebフレームワークであるPythonモジュール Flask を使用します。
Flaskについては下記を。

また、使用するPythonはMinicondaを使用します。
今回のソースは下記になります

Python

Flaskインストール

さくっとインストールします。
下記に従うだけです。

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
35
36
$ python -m pip install flask-restful
Collecting flask-restful
Downloading Flask_RESTful-0.3.5-py2.py3-none-any.whl
Collecting aniso8601>=0.82 (from flask-restful)
Downloading aniso8601-1.2.0.tar.gz (59kB)
100% |################################| 61kB 503kB/s
Requirement already satisfied: pytz in c:\program files\miniconda2\lib\site-packages (from flask-restful)
Requirement already satisfied: six>=1.3.0 in c:\program files\miniconda2\lib\site-packages (from flask-restful)
Collecting Flask>=0.8 (from flask-restful)
Downloading Flask-0.12-py2.py3-none-any.whl (82kB)
100% |################################| 92kB 1.1MB/s
Requirement already satisfied: python-dateutil in c:\program files\miniconda2\lib\site-packages (from aniso8601>=0.82->flask-restful)
Collecting Jinja2>=2.4 (from Flask>=0.8->flask-restful)
Downloading Jinja2-2.9.5-py2.py3-none-any.whl (340kB)
100% |################################| 348kB 1.5MB/s
Collecting Werkzeug>=0.7 (from Flask>=0.8->flask-restful)
Downloading Werkzeug-0.12-py2.py3-none-any.whl (312kB)
100% |################################| 317kB 1.6MB/s
Collecting click>=2.0 (from Flask>=0.8->flask-restful)
Downloading click-6.7-py2.py3-none-any.whl (71kB)
100% |################################| 71kB 2.3MB/s
Collecting itsdangerous>=0.21 (from Flask>=0.8->flask-restful)
Downloading itsdangerous-0.24.tar.gz (46kB)
100% |################################| 51kB 2.3MB/s
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->Flask>=0.8->flask-restful)
Downloading MarkupSafe-1.0.tar.gz
Building wheels for collected packages: aniso8601, itsdangerous, MarkupSafe
Running setup.py bdist_wheel for aniso8601 ... done
Stored in directory: C:\Users\TAKUYA\AppData\Local\pip\Cache\wheels\9e\75\c1\aa2de49c0d1ade4893e73009c0b7792ce89bc6c903a31f854b
Running setup.py bdist_wheel for itsdangerous ... done
Stored in directory: C:\Users\TAKUYA\AppData\Local\pip\Cache\wheels\fc\a8\66\24d655233c757e178d45dea2de22a04c6d92766abfb741129a
Running setup.py bdist_wheel for MarkupSafe ... done
Stored in directory: C:\Users\TAKUYA\AppData\Local\pip\Cache\wheels\88\a7\30\e39a54a87bcbe25308fa3ca64e8ddc75d9b3e5afa21ee32d57
Successfully built aniso8601 itsdangerous MarkupSafe
Installing collected packages: aniso8601, MarkupSafe, Jinja2, Werkzeug, click, itsdangerous, Flask, flask-restful
Successfully installed Flask-0.12 Jinja2-2.9.5 MarkupSafe-1.0 Werkzeug-0.12 aniso8601-1.2.0 click-6.7 flask-restful-0.3.5 itsdangerous-0.24

以上でインストールは完了です。

動作確認

最小のRESTfulなアプリが下記ドキュメントにあるのでファイルに保存します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
app.run(debug=True)

以上のソースを下記で起動します。

1
2
3
4
5
$ python Python.py
* Restarting with stat
* Debugger is active!
* Debugger PIN: 177-715-949
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Webブラウザで http://127.0.0.1:5000/ へアクセスすると、下記が表示されます。

1
2
3
{
"hello": "world"
}

jsonで返ってきています。
以上でFlaskのテストは完了です。

C#

SOAPであるならば、エンドポイントを参照して、クライアントクラス等の生成が自動でできますが、RESTにそんな便利機能はありません。

Microsoft.Net.Http

RESTful APIへのアクセスです。RESTful APIへのアクセスはライブラリを使って簡略化します。
幸いMicrosoft.Net.HttpというライブラリがあるのでNuGetで導入します。

Newtonsoft.Json

RESTful APIから返ってきたjsonの逆シリアライズをNewtonsoft.Jsonで実行します。
こちらもNuGetで導入します。

ソース

WPFです。Xaml側は省略します。
Xamlは、結果を表示するためのメッセージボックスを呼び出すButtonだけです。
ViewModelもButtonに対応するコマンドだけです。
まずは、先ほどのjsonに対するModelクラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Newtonsoft.Json;

namespace WPFPython.Models
{

[JsonObject("message")]
public sealed class MessageModel
{

[JsonProperty("hello")]
public string Hello { get; set; }

}
}

次はViewModelです。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Collections.ObjectModel;
using System.Net.Http;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using WPFPython.Models;
using WPFPython.ViewModels.Interfaces;

namespace WPFPython.ViewModels
{

public sealed class MainViewModel : ViewModelBase, IMainViewModel
{

#region フィールド

private readonly IMessageDialog _MessageDialog;

#endregion

#region コンストラクタ

public MainViewModel(IMessageDialog messageDialog)
{
if (messageDialog == null)
throw new ArgumentNullException(nameof(messageDialog));

this._MessageDialog = messageDialog;
}

#endregion

#region プロパティ

private RelayCommand _MessageCommand;

public RelayCommand MessageCommand
{
get
{
return this._MessageCommand ?? new RelayCommand(async () =>
{
var httpClient = new HttpClient();
var stringAsync = httpClient.GetStringAsync("http://127.0.0.1:5000/");
string result = await stringAsync;

var messageModel = Newtonsoft.Json.JsonConvert.DeserializeObject<MessageModel>(result);
this._MessageDialog.ShowMessage(messageModel.Hello);
});
}
}

#endregion

}
}

テスト

実際に実行してみます。
C#側を起動する前に、PythonでRESTサービス側を起動しておいてください。
ボタンを押下すると、REST APIのjsonを受け取り、そのjson唯一のメンバーである、Helloキーに対応する値をメッセージボックスで表示します。

Conclusion

IronPythonが期待外れに終わった今、Pythonの既存資産を生かす方法として、Webサービス化は有用な手段です。
単純に結果を呼び出すだけならRESTful APIは簡単で魅力的です。

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/WPF.Python4