##概要
C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。
これは、C++からPythonへの橋渡し「Cython」講座: C++のクラスオブジェクトを、Python側のクラスオブジェクトに引き渡すやり方。その② の続きになります。
コードはいつものごとく、ここのgithubにあげておきますので、ご覧ください!!
##状況説明
C++側でEnumクラスを定義し、それをクラスのプロパティとして持たせているとします。
この際、getterなどを使ってこのプロパティをもらうとき、返り値はもちろんEnumクラスになります。
これをCython化する場合、この返り値はPython側には定義されていないクラスなので、Cythonで定義後、パースして返す必要があります。
まず、C++とPythonでのEnumの書き方を簡単におさらいします。
###C++でのEnumクラスの書き方。
enum class Color{
RED = 0,
GREEN = 1,
BLUE = 2
};
###PythonでのEnumクラスの書き方。
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
###C++側からでEnumクラスをこのように追加しました。
これまで書いてきた TestClass1.h および TestClass1.cppに、Enumクラスのプロパティ(group)を追加します。
また、それに応じてgroupプロパティのgetter及びsetterを追加しました。
enum class EnumForTestClass1{
ZERO = 0,
ONE = 1
};
class TestClass1{
private:
TestClass2 property_test_class2;
EnumForTestClass1 group;
public:
// 〜省略
void set_group(EnumForTestClass1 group);
EnumForTestClass1 get_group();
EnumForTestClass1 TestClass1::get_group(){
return this->group;
}
void TestClass1::set_group(EnumForTestClass1 group){
this->group = group;
}
このgetterとsetterをcython化、つまりC++側のEnumクラスとPython側のEnumクラスを繋ぎます。
###C++側からのEnumクラスをPython側のEnumクラスに引き渡す方法。
例によって、
cytyon/test_class1.pxd
にC++側のEnumクラスを定義します。定義の仕方は下のファイルの通りに分けて行います。
その後、作ったgetter,setterの定義も追加しておきます。
cdef extern from "../cpp_library/TestClass1.h":
cdef cppclass EnumForTestClass1:
EnumForTestClass1()
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
cdef EnumForTestClass1 ZERO
cdef EnumForTestClass1 ONE
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
cdef cppclass TestClass1:
#〜省略
void set_group(EnumForTestClass1 group)
EnumForTestClass1 get_group();
また、同じ記載をcython/my_library.pxd
にも追加します。
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
cdef cppclass EnumForTestClass1:
EnumForTestClass1()
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
cdef EnumForTestClass1 ZERO
cdef EnumForTestClass1 ONE
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
cdef cppclass TestClass1:
#〜省略
void set_group(EnumForTestClass1 group)
EnumForTestClass1 get_group();
これが終了したら、次はcython/tests_class1.pyx
にgetter, setterの中身を記載していきます。
コードから先に書くと、以下のようになります。
cpdef enum Py_EnumForTestClass1:
Py_ZERO = 0
Py_ONE = 1
cdef class TestClass1Cython:
cdef TestClass1* ptr
def set_group(self, Py_EnumForTestClass1 group):
return self.ptr.set_group(<EnumForTestClass1> group)
def get_group(self):
cdef:
EnumForTestClass1* cpp_enum_for_test_class1 = new EnumForTestClass1()
cpp_enum_for_test_class1[0] = <EnumForTestClass1>self.ptr.get_group()
if(<int>cpp_enum_for_test_class1[0] == <int>Py_EnumForTestClass1.Py_ZERO):
return Py_EnumForTestClass1.Py_ZERO
elif(<int>cpp_enum_for_test_class1[0] == <int>Py_EnumForTestClass1.Py_ONE):
return Py_EnumForTestClass1.Py_ONE
まず、cpdef enum Py_EnumForTestClass1
でPythonから呼べるEnumクラスを定義。
次に、 setterに関しては、Py_EnumForTestClass1
をPython 側から受取り、<EnumForTestClass1>
でC++側の型にキャストすることでC++側の関数に渡しています。
また、getterに関しては少し冗長ですが、<EnumForTestClass1>self.ptr.get_group()
でC++側のEnumオブジェクトとして渡されたものを、でキャストし、そのintの値がPython側のPy_EnumForTestClass1
のどれに等しいのかをif文で判定させ、それの応じてPy_EnumForTestClass1
オブジェクトをリターンしています。
以上により、Python側のEnumクラスを、C++側のEnumクラスにパース、また、C++側のEnumクラスもPython側のEnumクラスにパースすることが可能になりました。
最後に、いつものように
python setup.py install
でコンパイルを行い、テストをしてみます。
import my_library as myl
if __name__ == "__main__":
cl1 = myl.test()
test1 = myl.Py_EnumForTestClass1.Py_ZERO
cl1.set_group(test1)
test2 = cl1.get_group()
print(test2)
結果として、
(myenv) root@e96f489c2395:/from_local/cython_practice# python test.py
0
となり、たしかにEnumクラスをC++のプロパティにset, そしてgetできたことが確認できました。
##まとめ
今回は、C++で書かれたライブラリをCython化してPythonから使えるようにする際の、C++で宣言されたEnumクラスをPython側のEnumクラスに引き渡す、というところを解説しました。
ここまでで大抵のC++側のカスタムオブジェクトがPython側に引き渡すことができるようになったと思います。
次回は、C++側で書くことの多い、関数のオーバーロードをCythonでどのように処理すればよいかについて書きたいと思います。
今回はこのへんで。
おわり。