Pythonを爆速にする「Cython」チュートリアル：　C++のクラスオブジェクトを、Python側のクラスオブジェクトに引き渡す。その②

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

これは、Pythonを爆速にする「Cython」チュートリアル：　C++のクラスオブジェクトを、Python側のクラスオブジェクトに引き渡す。その①　の続きになります。

コードは「[ここのgithub](https://github.com/kenmaro3/cython_practice)」にあげてあるので、ぜひご覧ください。

前回は、C++で書かれたライブラリをCython化してPythonから使えるようにする際の、C++で宣言された型をPython側に引き渡す、というところを解説しました。

intやdoubleそのものであったり、それらを要素に持つvectorなどはそのまま引き渡すと、Python側で適当な型に変換されることがわかりました。

今回は、C++のコードで自分で定義したクラスのオブジェクトをPython側に返す方法を解説します。


##状況説明
テスト用にこのような```TestClass2```を新しく作りました。
構成としては、```TestClass1```が、```TestClass2```のオブジェクトをプロパティとして持つような構成にします。そのプロパティを受け取るために、```getter```および```setter```、TestClass2をもらうコンストラクタを用意しておきます。



```cpp_library/TestClass2.h
namespace my_library
{
class TestClass2{
    private:
        int property_int;
    public:
        TestClass2(){};

};    
} // namespace my_library
```

```cpp_library/TestClass2.cpp

```

```cpp_library/TestClass1.h
#pragma once
#include <gmp.h>
#include <vector>
#include "TestClass2.h"

using namespace std;

namespace my_library
{
class TestClass1{
    private:
        TestClass2 property_test_class2;
    public:
        //省略
        TestClass1(TestClass2 test_class2);
        TestClass2 get_property_test_class2();
};    
} // namespace my_library
```


``` cpp_library/TestClass1.cpp
//省略
TestClass1::TestClass1(){
    this->property_test_class2 = TestClass2();
}

TestClass1::TestClass1(TestClass2 test_class2){
    this->property_test_class2 = test_class2;
}

TestClass2 TestClass1::get_property_test_class2(){
    return this->property_test_class2;
}

void TestClass1::set_property_test_class2(TestClass2 test_class2){
    this->property_test_class2 = test_class2;
}
```

## TestClass2をPythonで受け取るには？
###Cython側でPyhton用のラッパークラスを定義し、それに引き渡す。
実装はこのようになります。


```TestClass2Cython ``` は、
```TestClass2``` に対応するPythonに引き渡す用のラッパークラスです。


```cython/test_class2.pxd
cdef extern from "../cpp_library/TestClass2.h" namespace "my_library":
    cdef cppclass TestClass2:
        TestClass2()
```

```cython/test_class2.pyx
cdef class TestClass2Cython:
    cdef TestClass2* ptr

    def __cinit__(self):
        self.ptr = new TestClass2()

    def __deadaloc(self):
        del self.ptr
```

```cython/test_class1.pxd
from libcpp.vector cimport vector	
from libcpp.string cimport string

from test_class2 cimport *

cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
    cdef cppclass TestClass1:
        TestClass1()
        TestClass1(TestClass2 test_class2)
        void test_function1()
        void gmp_print_test()
        int test_sum(int x, int y)
        double test_sum_double(double x, double y)
        vector[int] test_vector_int(vector[int] x, int y)

        void set_property_test_class2(TestClass2 test_class2)
        TestClass2 get_property_test_class2()
```


ここで重要なのが、

- C++側のクラスオブジェクトを```cdef TestClass2 cpp_test_class2```と宣言し、
- Python側のクラスオブジェクトを```test_class2 = TestClass2Cython()```と宣言。
- C++からの返り値をまずC++のクラスオブジェクトに格納し、そのあとに、```test_class2.ptr[0] = cpp_test_class2```で、C++側の返り値をPython側に受け渡すことです。

ここで、```test_class2.ptr```はtest_class2.pyxに```cdef TestClass2* ptr```と定義されており、```self.ptr[0]```はC++のクラスオブジェクトの実体を意味しています。
C++側の```get_property_test_class2()```ではTestClass2のクラスオブジェクトの実体を返していたので、```self.ptr[0]``` とする必要があったわけです。


```cython/test_class2.pyx
    def get_property_test_class2(self):
        cdef:
            TestClass2 cpp_test_class2
        
        test_class2 = TestClass2Cython()
        cpp_test_class2 = self.ptr.get_property_test_class2()
        test_class2.ptr[0] = cpp_test_class2
        return test_class2
```

いつものように　```python setup.py install```でコンパイル後、


```bash
(myenv) root@e96f489c2395:/from_local# python 
Python 3.6.3 (default, Jan 30 2020, 06:37:54) 
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import my_library
>>> test = my_library.test()
>>> test.get_property_test_class2()
<my_library.TestClass2Cython object at 0x7f776003d240>
```

となり、確かにC++側からの```TestClass2```が```TestClass2Cython```に渡されてPython側で認識できているのが確認できます。


## まとめ
C++側で定義されたクラスのオブジェクトに関しては、Cython側に受け皿となるクラスを定義し、関数の返り値をその型に変換して返す操作が必要となることがわかりました。

言われてみれば当たり前なのですが、意外と苦労するところなので、役に立てば嬉しいです。

次は、今回とは逆に、Python側のクラスオブジェクトをCython経由でC++に引き渡す方法を解説したいと思います。


今回はこの辺で。

おわり。