Pythonを爆速にする「Cython」チュートリアル：　C++のコードで定義したEnumクラスをPython のEnumにパースするやり方。

##概要
C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。

これは、[C++からPythonへの橋渡し「Cython」講座：　C++のクラスオブジェクトを、Python側のクラスオブジェクトに引き渡すやり方。その②](https://qiita.com/kenmaro/items/1275b72c8a99f0b6a8c2)　の続きになります。

コードはいつものごとく、[ここのgithub](https://github.com/kenmaro3/cython_practice)にあげておきますので、ご覧ください！！

##状況説明
C++側でEnumクラスを定義し、それをクラスのプロパティとして持たせているとします。
この際、getterなどを使ってこのプロパティをもらうとき、返り値はもちろんEnumクラスになります。
これをCython化する場合、この返り値はPython側には定義されていないクラスなので、Cythonで定義後、パースして返す必要があります。

まず、C++とPythonでのEnumの書き方を簡単におさらいします。

###C++でのEnumクラスの書き方。

```cpp

enum class Color{
    RED = 0,
    GREEN = 1,
    BLUE = 2
};
```


###PythonでのEnumクラスの書き方。

```python

class Color(Enum):
    RED = 0
    GREEN = 1
    BLUE = 2
```


###C++側からでEnumクラスをこのように追加しました。
これまで書いてきた　TestClass1.h および TestClass1.cppに、Enumクラスのプロパティ（group）を追加します。
また、それに応じてgroupプロパティのgetter及びsetterを追加しました。

```cpp_library/TestClass1.h

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();
```


```cpp_library/TestClass1.cpp

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の定義も追加しておきます。


```cython/test_class1.pxd

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``` にも追加します。

```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の中身を記載していきます。
コードから先に書くと、以下のようになります。

```cython/tests_class1.pyx
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>でキャストし、そのintの値がPython側の```Py_EnumForTestClass1 ``` のどれに等しいのかをif文で判定させ、それの応じて```Py_EnumForTestClass1``` オブジェクトをリターンしています。


以上により、Python側のEnumクラスを、C++側のEnumクラスにパース、また、C++側のEnumクラスもPython側のEnumクラスにパースすることが可能になりました。

最後に、いつものように
```python setup.py install```
でコンパイルを行い、テストをしてみます。

```test.py
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)
```

結果として、

```bash
(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でどのように処理すればよいかについて書きたいと思います。


今回はこのへんで。


おわり。