前回は簡単な関数を呼び出してみました。

Introduction

今回の目的は、引数を渡し、その戻りを表示します。
ただ、単純に文字列や数値を渡すのは面白くありません。
今回は配列を渡して、配列を返します。
なんと言っても、Pythonは配列、リスト、タップルの操作を得意としており、またそれらを操作するライブラリが豊富です。
すなわち、配列をPythonで処理させ、その結果をC#で受け取ることができれば、より実践的なアプリが作成できます。
今回は、2つの数値を指定し、その数値を数列の先頭、末尾として扱い、差が1の階差数列を生成します。
その階差数列をPythonに渡し、各要素を2倍にした配列をC#に返し、最終的に配列内の要素の総和を返すアプリです。
例えば、1、10を指定すれば、1,2,…9,10が生成され、最終的な値は55*2の110と表示されます。
今回のソースは下記になります

Python側

Python側のプロジェクトを作成します。
Pythonのソースは下記になります。

1
2
3
4
5
6
7
8
class PythonTest():
def twice(selt, array):
list = [0 for i in range(len(array))]
i = 0
for x in array:
list[i] = x * 2
i += 1
return list;

Pythonの文法をかなり忘れているので、ググりながら作成しました。情けない。
まず、list変数は、引数に渡ってきた配列arrayの長さの個数を持ち、かつ各要素は0で初期化されます。
次に、渡ってきた配列arrayをループで各要素、つまりxにアクセス、そのxを2倍にし、先頭から順にlistに格納します。
そして、listを返します。

C#側

今回もWPFです。Xaml側は省略します。
数列の開始数値と終了数値を入力できるTextBox、結果を表示するメッセージボックスを表示するボタンだけです。
IronPythonの参照方法、Microsoft.Scripting.Hosting.ScriptRuntime 型IronPython.Hosting.Python.CreateRuntime メソッドUseFile メソッドの説明は前回の記事を参照してください。
今回は、Pythonと配列をやりとりする箇所のみ説明します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public RelayCommand MessageCommand
{
get
{
return this._MessageCommand ?? new RelayCommand(() =>
{
var array = Enumerable.Range(this._Start, this._End - this._Start + 1).ToArray();
var sum = array.Sum();
var list = this._PythonWrapper.Twice(array);

var message = $"Initial total:{sum}, final total:{list.Sum()}";
this._MessageDialog.ShowMessage(message);
});
}
}

_PythonWrapper.Twiceが今回、Pythonスクリプトの関数を呼び出します。
arrayはint[]です。
そしてlistもint[]です。
では、PythonWrapperの中身です。

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
57
58
59
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Scripting.Hosting;
using WPFPython2.ViewModels.Interfaces;

namespace WPFPython2.ViewModels
{

public sealed class PythonWrapper : IPythonWrapper
{

#region フィールド

private readonly ScriptRuntime _ScriptRuntime;

private dynamic _PythonObject;

private dynamic _PythonTest;

private readonly string _Path;

#endregion

#region コンストラクタ

public PythonWrapper(ScriptRuntime scriptRuntime, string path)
{
if (scriptRuntime == null)
throw new ArgumentNullException(nameof(scriptRuntime));
if (path == null)
throw new ArgumentNullException(nameof(path));

if (!File.Exists(path))
throw new FileNotFoundException(path);

this._ScriptRuntime = scriptRuntime;
this._Path = path;
this._PythonObject = this._ScriptRuntime.UseFile(this._Path);
this._PythonTest = this._PythonObject.PythonTest();
}

#endregion

#region メソッド

public int[] Twice(int[] array)
{
var list = this._PythonTest.twice(array);
return ((IList<object>)list).Cast<int>().ToArray();
}

#endregion

}

}

コンストラクタは前回と同じです。Twiceメソッドだけが肝です。
まず、普通にint[]をPython側に渡しています。
しかし、戻ってきたlist変数はそのまま返すことはできません。
この変数の正確な型は、IronPython.Runtime.List という型です。
この変数を GetType().GetMembers() するとわかるのですが、

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Void set_Item(IronPython.Runtime.Slice, System.Object)
Void set_Item(Int32, System.Object)
Void set_Item(System.Numerics.BigInteger, System.Object)
Void set_Item(System.Object, System.Object)
Void __init__()
Void __init__(System.Collections.IEnumerable)
Void __init__(System.Collections.ICollection)
Void __init__(IronPython.Runtime.SetCollection)
Void __init__(IronPython.Runtime.FrozenSetCollection)
Void __init__(IronPython.Runtime.List)
Void __init__(System.String)
Void __init__(IronPython.Runtime.CodeContext, System.Object)
System.Object __new__(IronPython.Runtime.CodeContext, IronPython.Runtime.Types.PythonType)
System.Object __new__(IronPython.Runtime.CodeContext, IronPython.Runtime.Types.PythonType, System.Object)
System.Object __new__(IronPython.Runtime.CodeContext, IronPython.Runtime.Types.PythonType, System.Object[])
System.Object __new__(IronPython.Runtime.CodeContext, IronPython.Runtime.Types.PythonType, System.Collections.Generic.IDictionary`2[System.Object,System.Object], System.Object[])
IronPython.Runtime.List op_Addition(IronPython.Runtime.List, IronPython.Runtime.List)
IronPython.Runtime.List op_Multiply(IronPython.Runtime.List, Int32)
IronPython.Runtime.List op_Multiply(Int32, IronPython.Runtime.List)
System.Object op_Multiply(IronPython.Runtime.List, IronPython.Runtime.Index)
System.Object op_Multiply(IronPython.Runtime.Index, IronPython.Runtime.List)
System.Object op_Multiply(IronPython.Runtime.List, System.Object)
System.Object op_Multiply(System.Object, IronPython.Runtime.List)
Int32 __len__()
System.Collections.IEnumerator __iter__()
System.Collections.IEnumerator __reversed__()
Boolean __contains__(System.Object)
System.Object InPlaceAdd(System.Object)
IronPython.Runtime.List InPlaceMultiply(Int32)
System.Object InPlaceMultiply(IronPython.Runtime.Index)
System.Object InPlaceMultiply(System.Object)
System.Object __getslice__(Int32, Int32)
Void __setslice__(Int32, Int32, System.Object)
Void __delslice__(Int32, Int32)
System.Object get_Item(IronPython.Runtime.Slice)
Void __delitem__(Int32)
Void __delitem__(System.Object)
Void __delitem__(IronPython.Runtime.Slice)
Void append(System.Object)
Int32 count(System.Object)
Void extend(IronPython.Runtime.List)
Void extend(IronPython.Runtime.PythonTuple)
Void extend(System.Object)
Int32 index(System.Object)
Int32 index(System.Object, Int32)
Int32 index(System.Object, Int32, Int32)
Int32 index(System.Object, System.Object)
Int32 index(System.Object, System.Object, System.Object)
Void insert(Int32, System.Object)
Void Insert(Int32, System.Object)
System.Object pop()
System.Object pop(Int32)
Void remove(System.Object)
Void reverse()
Void sort(IronPython.Runtime.CodeContext)
Void sort(IronPython.Runtime.CodeContext, System.Object)
Void sort(IronPython.Runtime.CodeContext, System.Object, System.Object)
Void sort(IronPython.Runtime.CodeContext, System.Object, System.Object, Boolean)
System.Object get_Item(Int32)
System.Object get_Item(System.Numerics.BigInteger)
System.Object get_Item(System.Object)
Void RemoveAt(Int32)
Boolean Contains(System.Object)
Void Clear()
Int32 IndexOf(System.Object)
Int32 Add(System.Object)
Int32 get_Count()
Void CopyTo(System.Array, Int32)
System.Collections.IEnumerator GetEnumerator()
System.String __repr__(IronPython.Runtime.CodeContext)
Void CopyTo(System.Object[], Int32)
Boolean Remove(System.Object)
System.Object op_GreaterThan(IronPython.Runtime.List, System.Object)
System.Object op_LessThan(IronPython.Runtime.List, System.Object)
System.Object op_GreaterThanOrEqual(IronPython.Runtime.List, System.Object)
System.Object op_LessThanOrEqual(IronPython.Runtime.List, System.Object)
System.String ToString()
Boolean Equals(System.Object)
Int32 GetHashCode()
System.Type GetType()
Void .ctor()
System.Object Item [IronPython.Runtime.Slice]
System.Object Item [Int32]
System.Object Item [System.Numerics.BigInteger]
System.Object Item [System.Object]
Int32 Count
System.Object __hash__

ところどころ、Pythonで提供している関数名が見つかります。また、Int32 Countやインデクサがあることから、System.Collections.IListを実装していそうなことはわかります。
実際にキャストもできます。
ですが、Pythonでは型はあってないようなものですし、要素の型がわからないため、明示的にキャストして呼び出し元に返しています。
キャストこそ面倒ですが、配列を渡す、呼び出すことに全く問題はありません。

テスト

実際に実行してみます。
サンプルソースをビルドすると実行ディレクトリに、pythonsフォルダが生成され、Python2.pyファイルがコピーされます。
このファイルはPythonプロジェクトのファイルそのものです。
実行するとButtonが1つとTextBoxが2つ。。

ボタンを押すと、Python2.pyのtwiceの戻りの内容の総和が表示されます。

アプリを終了し、Python2.pyのtwice関数内の、2倍の部分を3に変更したりすれば、今回も内容が変わることがわかります。

Conclusion

今回も簡単に実行できました。
異なる言語間のやりとりで面倒なことの筆頭は配列の扱いだと思います。例えば、C#とC/C++におけるポインタとか。
しかし、そんな面倒なところを感じさせないところは、Pythonがグルー言語と言われるところでしょう。

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/WPF/02_WPF.Python2