LoginSignup
2
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-26

概要

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を追加しました。

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の値が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)

結果として、

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

今回はこのへんで。

おわり。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2