LoginSignup
9
7

More than 3 years have passed since last update.

Pythonを爆速にする「Cython」チュートリアル: C++側の関数が参照渡しされているときのハンドリング。

Posted at

概要

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

これは、Pythonを爆速にする「Cython」チュートリアル: C++側の関数がオーバーロードを持つとき。 の続きになります。

コードは「ここのgithub」にあげてあるので、ぜひご覧ください。

前回は、C++側の関数がオーバーロードをしているとき(同じ関数名で、引数に異なるものが入るような構成のとき)の、Cython化の解説しました。

今回は、C++側の関数が、引数として値渡しではなく参照渡しのときのCython側でのハンドリング仕方を解説したいと思います。
あまり新しいことは出てきませんが、実際Cythonを書こうとするとエラーに悩まされてしまうことが多いので、そのようなときに是非ご覧ください!!

状況説明

  • C++側の関数がオーバーロードされていたので、前回の記事のようにキャストを使って解決していた。
  • C++側に実装された関数の引数の中に、参照渡しのものが含まれている。
  • 引数が参照型だとエラーでるやん。

実際に試してみます。

cpp_library/TestClass1.h
class TestClass1{
    private:
        TestClass2 property_test_class2;
        EnumForTestClass1 group;

    public:
        //~省略
        static void print_vector(vector<int> &x);
        static void print_vector(vector<double> &x);

前回と違うところは、print_vectorの引数のxが、値渡しから参照渡しになっているところです。

cpp_library/TestClass1.cpp
void TestClass1::print_vector(vector<int> &x){
    for(int i=0; i<x.size(); i++){
        cout << x[i] << " ";
    }
    cout << endl;
}

void TestClass1::print_vector(vector<double> &x){
    for(int i=0; i<x.size(); i++){
        cout << x[i] << " ";
    }
    cout << endl;
}

これをいつものようにpython setup.py install でbuildしようとすると、以下のエラーがでます。


./cython/my_library.cpp:2414:69: error: cannot bind non-const lvalue reference of type 'std::vector<int>&' to an rvalue of type 'std::vector<int>'
     __pyx_t_1 = __Pyx_void_to_None(__pyx_v_testclass1.print_vector(((std::vector<int> )__pyx_t_4))); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 53, __pyx_L1_error)

キャストをきちんとしているはずなのに、参照渡しになると怒られます。

そこで、解決策として、

キャストではなく実体をその場で作ります。

どういうことかというと、

cython/test_class1.pyx
    @staticmethod
    def print_vector(list x):
        cdef:
            TestClass1 testclass1
            vector[int] vec_int
            vector[double] vec_double

        if isinstance(x[0], int):
            vec_int.resize(len(x))
            for i in range(len(x)):
                vec_int[i] = <int>x[i]
            return testclass1.print_vector(vec_int)
        elif isinstance(x[0], float):
            vec_double.resize(len(x))
            for i in range(len(x)):
                vec_double[i] = <double>x[i]
            return testclass1.print_vector(vec_double)
        else:
            raise Exception('TypeError')

とします。ここで、cdefにより、vector[int]や、vector[double]をその場で作り、それを関数に渡します。
たぶん、さっき出たエラーは、参照渡しなのに、本当に実体存在してるかわからんやん!困るで!!!
というエラーだったんだと自分は解釈しています。

したがって、実体を用意してあげて、安心させるといいわけです。

こうすると、例によってpython setup.py installにより、ビルドが通ります。

これにより、テストコードを以下のように書くことで、

test.py
import my_library as myl

if __name__ == "__main__":

    cl1 = myl.test()
    x = [1,2,3]
    y = [1.1, 2.2, 3.3]
    cl1.print_vector(x)
    cl1.print_vector(y)

きちんと関数をコールできました。

(myenv) root@e96f489c2395:/from_local/cython_practice# python test.py 
1 2 3 
1.1 2.2 3.3 

まとめ

今回は、C++側の関数が、引数として値渡しではなく参照渡しのときのCython側でのハンドリング仕方を解説しました。

今回はこのへんで。

おわり。

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