##概要
C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。
これは、Pythonを爆速にする「Cython」チュートリアル: C++のクラスオブジェクトを、Python側のクラスオブジェクトに引き渡す。その① の続きになります。
コードは「ここのgithub」にあげてあるので、ぜひご覧ください。
前回は、C++で書かれたライブラリをCython化してPythonから使えるようにする際の、C++で宣言された型をPython側に引き渡す、というところを解説しました。
intやdoubleそのものであったり、それらを要素に持つvectorなどはそのまま引き渡すと、Python側で適当な型に変換されることがわかりました。
今回は、C++のコードで自分で定義したクラスのオブジェクトをPython側に返す方法を解説します。
##状況説明
テスト用にこのようなTestClass2
を新しく作りました。
構成としては、TestClass1
が、TestClass2
のオブジェクトをプロパティとして持つような構成にします。そのプロパティを受け取るために、getter
およびsetter
、TestClass2をもらうコンストラクタを用意しておきます。
namespace my_library
{
class TestClass2{
private:
int property_int;
public:
TestClass2(){};
};
} // namespace my_library
#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
//省略
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に引き渡す用のラッパークラスです。
cdef extern from "../cpp_library/TestClass2.h" namespace "my_library":
cdef cppclass TestClass2:
TestClass2()
cdef class TestClass2Cython:
cdef TestClass2* ptr
def __cinit__(self):
self.ptr = new TestClass2()
def __deadaloc(self):
del self.ptr
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]
とする必要があったわけです。
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
でコンパイル後、
(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++に引き渡す方法を解説したいと思います。
今回はこの辺で。
おわり。