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

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

これは、Pythonを爆速にする「Cython」チュートリアル：　C++のコードが、ライブラリに依存しているときのsetup.pyの書き方。　の続きになります。

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

前回までで、C++のコードがライブラリに依存している時、それをCython化するためのsetup.pyの書き方を解説しました。
そのときに使用したC++側の関数は、void型のみでした。しかしながら、もちろん関数はvoidを使用するよりも、なにかしらの型の値を返したりする関数をよく書くと思います。
今回は、void型でなく、C++側の型を返す関数を、どうCython化し、Python側に引き渡すかについて解説していきます。ここがわかるとかなりやれることが増えるので、ぜひご覧ください。

当たり前ですが、Cythonを用いてC++のライブラリをPythonで使えるようにラップするとき、C++のクラス定義をPython側は自動的にパースしてくれません。そこで、Cython側できちんとクラスをPython用に定義し、C++側と繋いであげないといけません。

慣れれば簡単に書くことができますが、最初は何をやって良いかわかりにくいので、そこを解説していきます。

##状況説明

###intやdoubleを引き渡したい。
例としてintを返す```test_sum```と、doubleを返す```test_sum_double```をC++側に書きます。

```cpp_library/TestClass1.h
#include <gmp.h>

namespace my_library
{
class TestClass1{
    public:
        TestClass1();
        void test_function1();
        static void gmp_print_test();
        static int test_sum(int x, int y);
        static double test_sum_double(double x, double y);
};    
} // namespace my_library
```

```cpp_library/TestClass1.cpp
#include <iostream>
#include "TestClass1.h"
using namespace std;

namespace my_library{

TestClass1::TestClass1(){};
void TestClass1::test_function1(){
    cout << "printed from cpp function" << endl;
}
void TestClass1::gmp_print_test(){
    mpz_t test;
    mpz_init(test);
    mpz_set_ui(test, 1);
    gmp_printf("print : %Zd \n", test);
}

int TestClass1::test_sum(int x, int y){
    return x+y;
}

double TestClass1::test_sum_double(double x, double y){
    return x+y;
}

}
```

このとき、Cythonのコードはこうします。

```cython/test_class1.pxd
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
    cdef cppclass TestClass1:
        TestClass1()
        void test_function1()
        void gmp_print_test()
        int test_sum(int x, int y)
        double test_sum_double(double x, double y)
```

```cython/test_class1.pyx
import cython
cimport cython

cdef class TestClass1Cython:
    cdef TestClass1* ptr

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

    def __deadaloc(self):
        del self.ptr

    def test_function1_cython(self):
        self.ptr.test_function1()
    
    @staticmethod
    def gmp_print_test():
        cdef TestClass1 testclass1
        testclass1.gmp_print_test()

    @staticmethod
    def test_sum(int x, int y):
        cdef TestClass1 testclass1
        return testclass1.test_sum(x, y)

    @staticmethod
    def test_sum_double(float x, float y):
        cdef TestClass1 testclass1
        return testclass1.test_sum_double(x, y)

```



###intやdoubleが入ったvectorを引き渡したい。

vector<int> を返すようなC++の実装がこのようにあったとします。
ただリストをもらって、それを定数倍したものを返すテスト関数です。
クラスの変更部分だけ書きます。（vector<double>のパターンでも実装は同じなので、割愛します。）

```cpp_library/TestClass1.h
static vector<int> test_vector_int(vector<int> x, int y);
```

```cpp_library/TestClass.cpp

vector<int> TestClass1::test_vector_int(vector<int> x, int y){
    vector<int> result;
    result.resize(x.size());
    for(int i=0; i<x.size(); i++){
        result[i] = x[i]*y;
    }

    return result;
}
```



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

cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
    cdef cppclass TestClass1:
        TestClass1()
        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)
```

上記の```from libcpp.vector cimport vector  ```でC++側の```vector```を認識してくれるので、Cython側であまりやることはありません。先ほどintやdoubleを返すような関数と同じ感覚で実装できます。

```cython/test_class1.pyx
    @staticmethod
    def test_vector_int(list x, int y):
        cdef TestClass1 testclass1
        return testclass1.test_vector_int(x,y)
```

実際に```python setup.py install```すると、コンパイルが通り、Python側からこれらの関数が呼べることが確認できます。

```bash
(myenv) root@e96f489c2395:/from_local/cython_practice# 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
>>> my_library.TestClass1Cython.test_vector_int([1,2,3], 3)
[3, 6, 9]
```

###C++ライブラリでのカスタムクラスを引き渡したい。
次に記事で書きます。

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

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

次回はC++のコードで自分で定義したクラスのオブジェクトをPython側に返す方法を解説します。intやdouble, それらを要素に持つvectorよりも少しだけ複雑にはなりますが、実際のプログラムではこのような状況が多いので、ぜひご覧ください。


今回はこの辺で。

おわり。