4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

##概要
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++側に書きます。

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

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側からこれらの関数が呼べることが確認できます。

(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よりも少しだけ複雑にはなりますが、実際のプログラムではこのような状況が多いので、ぜひご覧ください。

今回はこの辺で。

おわり。

4
4
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
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?