##概要
C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。
これは、Pythonを爆速にする「Cython」チュートリアル: C++のコードが、ライブラリに依存しているときのsetup.pyの書き方。 の続きになります。
コードは「ここのgithub」にあげてあるので、ぜひご覧ください。
前回までで、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++側に書きます。
#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
#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のコードはこうします。
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)
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 を返すようなC++の実装がこのようにあったとします。
ただリストをもらって、それを定数倍したものを返すテスト関数です。
クラスの変更部分だけ書きます。(vectorのパターンでも実装は同じなので、割愛します。)
static vector<int> test_vector_int(vector<int> x, int y);
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;
}
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を返すような関数と同じ感覚で実装できます。
@staticmethod
def test_vector_int(list x, int y):
cdef TestClass1 testclass1
return testclass1.test_vector_int(x,y)
実際にpython setup.py install
すると、コンパイルが通り、Python側からこれらの関数が呼べることが確認できます。
(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よりも少しだけ複雑にはなりますが、実際のプログラムではこのような状況が多いので、ぜひご覧ください。
今回はこの辺で。
おわり。