LoginSignup
10
6

More than 3 years have passed since last update.

Pythonを爆速にする「Cython」チュートリアル: 基本的な構成編

Last updated at Posted at 2020-01-29

概要

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

闇の深いライブラリの依存関係、発狂したくなる環境構築。
どうすればいいかわからない型の変換。
自分がpythonを書いているのか、c++を書いているのか、何をやっているのかわからなくなるあの絶望感。

少しだけcythonを触る時間が増えたので、少しずつまとめて行きたいと思います。
なお、私はpython, c++, cythonどれも初心者レベルなので、間違いなどありましたらどうぞご指摘よろしくお願いいたします。

内容

今回は、とても簡単なc++のコードを、cythonizeしてライブラリとしてpythonから呼ぶことを目標にしてみます。

ミニマムなフォルダ構成は、以下になります。

hoge@~/Documents/cython_test> tree .
.
├── cython
│   ├── library_cython.pyx
│   ├── test_library.pxd
│   └── test_library.pyx
├── my_library
│   ├── test.cpp
│   └── test.h
└── setup.py

2 directories, 6 files

setup.py

まず、ライブラリのビルドに必要なsetup.pyです。

 test_library がpythonで読み込むライブラリの名前になります。

つまり、ビルドした後は import test_library とかけるわけです。

setup.py
from setuptools import setup, Extension, find_packages
from Cython.Build import cythonize
from Cython.Distutils import build_ext
from distutils import sysconfig

ext_modules = [
    Extension(
        "test_library", sources=[
            "./cython/test_library.pyx",
            "./my_library/test.cpp"
        ],
        language="c++"
    )
]

setup(
    name = "test_library",
    cmdclass = {"build_ext": build_ext},
    ext_modules= cythonize(ext_modules)
)

このとき、

    Extension(
        "test_library", sources=[
            "./cython/test_library.pyx",

setup(
    name = "test_library",

で出てくる3つのtest_libraryのところの名前は一緒にしておきましょう。ここでハマりました。

c++側のコード

c++側に、TestClassというクラスを作っておきます。これをcython経由でpythonから呼びます。
簡単のため、TestClassはコンストラクタと、test_function1()というメソッドだけ持たせておきます。
このとき、namespaceをきちんとつけておきましょう。理由はよくわかりませんが、cython化するときにこの方が都合が良くなります。ここでは、my_libraryというnamespaceにしました。

test.h
namespace my_library
{
class TestClass{
    public:
        TestClass();
        void test_function1();
};    
} // namespace my_library


ヘッダで定義された関数の中身を書きます。test_function1()は、単にプリントをする関数にしました。

test.cpp
#include <iostream>
#include "test.h"
using namespace std;

namespace my_library{

TestClass::TestClass(){};
void TestClass::test_function1(){
    cout << "printed from cpp function" << endl;
}


}

cython側のコード

setup.pyにも出てくるように、test_library.pyx が、メインのcythonファイルになります。
後述のlibrary_cython.pyxをインクルードしています。

test_library.pyx
import cython
cimport cython

include "library_cython.pyx"

def test():
    return TestClassCython()

 pxdファイルは、test_library.pyxに必要な情報をc++から渡すための定義ファイルのようなものです。ここで実際に、c++のヘッダで定義されたことを再度、cythonの形で書く必要があります。

test_library.pxd
cdef extern from "../my_library/test.h" namespace "my_library":
    cdef cppclass TestClass:
        TestClass()
        void test_function1()

この、library_cython.pyxで、python側で実際に呼び出すpythonの(cythonの?)クラスを定義して行きます。最初戸惑うのは、クラスのメンバ変数として、c++で定義されたクラスのポインタを保持しておくことです。コンストラクタでは、c++側のクラスをコンストラクトし、そのアドレスをこのメンバ変数のptrに渡します。デストラクタは、delでこのptrを消去しています。

また、test_function1_cython()メソッドは、 ptr.test_function1()とすることで、c++側で定義されたメソッドを内部的に呼び出しています。

library_cython.pyx
import cython
cimport cython

cdef class TestClassCython:
    cdef TestClass* ptr

    def __cinit__(self):
        self.ptr = new TestClass()

    def __deadaloc(self):
        del self.ptr

    def test_function1_cython(self):
        self.ptr.test_function1()

ビルドできたら、自分を褒めてあげよう。

以上が準備できた段階で、


python setup.py install

とします。うまくビルドできたら、pythonのインタプリタに入り、


Python 3.6.7 (default, Nov 16 2019, 21:57:19) 
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test_library
>>> a = test_library.test()
>>> a.test_function1_cython()
printed from cpp function
>>> 

とできたら、ちょっと嬉しいよね。

まとめ

というわけで、cythonを使って、c++で書いたクラスを、pythonからライブラリを経由して呼ぶことができるところまでやってみました。
これはまだ序の口で、例えばc++でコンパイルの必要なライブラリを使っている時など、依存関係がややこしくなったりしますが、もうすこし闇の深いケースに関してはまた書きたいと思います。

参考になった方がいましたら、「いいね」や「コメント」をお待ちしております!!

おわり。

10
6
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
10
6