UTALI

みんなの役に立つ情報をどんどん公開していきます

【Cython】PythonからC++のクラスを利用して爆速化する

こちらも参考に

www.utali.io

Python with C++

Cythonを利用することでC++のコードをPythonから簡単に利用することができます。

C言語での使用例はたくさんあるのですが、C++はほとんど見当たらないのでこの記事を書きました。

ハイパフォーマンスPython

ハイパフォーマンスPython

用意するファイルは4つ

  • test.h
  • test.cpp
  • test_.pyx
  • setup.py

test.h

利用したいC++コードのヘッダファイルです。

test.cpp

利用したいC++の実装ファイルです

test_pyx

PythonからC++コードを利用するためのメソッドを定義するファイルです。 必ずC++コードと違う名前をつけてください。

setup.py

Cythonライブラリを作成するためのファイルです。

この4つのファイルを同一ディレクトリに配置してください。

今回利用するC++ライブラリについて

Cythonの利用法にフォーカスしたいので、実用性ゼロのコードを利用します。 test.hとtest.cppです。

test.h

#ifndef __TEST_
#define __TEST_

#include <iostream>

using namespace std;

namespace test{
 class Test {
  private:
    int x,y,z;
    void add(int _x);
  public: 
    int getX();
    void addX(int _x);

    Test(int _x,int _y,int _z);

    ~Test();
 };
}
#endif

test.cpp

#include <iostream>
#include "test.h"

using namespace std;

namespace test{
    Test::Test(int _x,int _y,int _z){
        x = _x;
        y = _y;
        z = _z;
    }

    Test::~Test(){}

    void Test::add(int _x){
        x += _x;
    }

    void Test::addX(int _x){
        add(_x);
    }
    int Test::getX(){
        return x;
    }
}

内容については特に説明しません。

test_.pyx

この内容が非常に重要なので、刮目してください。 最初の一行目に読み込むヘッダファイルとそのクラスに割り当てられた名前空間を指定します。 次の行にはそのヘッダファイル内で定義されていて、かつPythonから利用したいクラスを宣言します。

インデントの後の行は利用したいメソッドです。今回はコンストラクタと2つのメソッドを利用します。引数の型を指定するのをお忘れなく。

次のcdef classにはPythonから呼び出すときのクラス名を定義します。 なので、Pythonからモジュールを呼び出すときは、import {モジュール名} とした後に {モジュール名}.{cdef classで定義したクラス}となります。

そしてインデントした後にcdef cppclassで定義したクラスを宣言します。ポインタ形式にしないとうまくいきません。

なお呼び出しの形式は->にする必要はありません。Cythonのトランスパイラが勝手に変換をします。

次にdef cinit(self,int x,int y,int z):でコンストラクタを定義、 def __dealloc(self):でデストラクタを定義します。

それ以降の行はPythonからC++のメソッドを呼び出すときのラッパになります。 ここで宣言しておかないと呼び出せません。

cdef extern from "test.h" namespace "test":
   cdef cppclass Test:
       Test(int x,int y,int z)
       void addX(int x)
       int getX()
cdef class CyTest:

   cdef Test* _test
   
   def __cinit__(self,int x,int y,int z):
       self._test = new Test(x,y,z)
   def __dealloc(self):
       del self._test
   def add_x(self,int x):
       return self._test.addX(x)
   def get_x(self):
       return self._test.getX()

setup.py

注意点としては、extensionの後のライブラリ名とpyxファイルは必ずC++のファイルと違う名前にすること、ヘッダファイルではなく実装ファイルを指定することを忘れないでください。 あと、language=“c++"を指定するのを忘れずに

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize

setup(
  name = "test_",
  ext_modules = cythonize(
    Extension("test_",                                # the extension name
              sources=["test_.pyx", "test.cpp"], # the Cython source and
                                                  # additional C++ source files
              extra_compile_args=["-std=c++1y"],
              language="c++",                        # generate and compile C++ code
             )
  ),
  cmdclass = {'build_ext': build_ext},
)

ここまで完了したら、setup.pyを呼び出して、Cythonライブラリを作成します。

python3 setup.py build_ext -if

すると以下のようなファイルが作成されます。これが利用可能なCythonライブラリです。

test_.cpython-35m-darwin.so

REPLでうまく動くか試してみましょう。

python3
>>> import test_
>>> r = test_.CyTest(1,2,3)
>>> r.get_x()
1

>>> r.add_x(1)

>>> r.get_x()

2

うまくいきました。

うまく活用すれば、Pythonの欠点である実行速度の遅さをカバーできるでしょう。

Cythonを使うなら以下の参考書がおすすめです。↓

Cython ―Cとの融合によるPythonの高速化

Cython ―Cとの融合によるPythonの高速化