Help us understand the problem. What is going on with this article?

Pythonを爆速にする「Cython」チュートリアル: C++のコードがライブラリに依存しているとき。setup.pyを書く。

概要

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

これは、 Pythonを爆速にする「Cython」チュートリアル: C++のコードがライブラリに依存しているとき。まずはCMake。 の続きになります。

ここまでで、gmpライブラリに依存したC++のプログラムを、cmakeを使って快適にコンパイルするところまでを見てきました。ここからやっと本題ですが、このC++プログラムをcythonizeするコードを書き、それをsetup.pyでコンパイルしていきます。

ここで使用したコードは、ここのgithub にありますので、興味のある方はご覧ください!

状況説明

前回の記事では、まずそもそもsetup.pyを書くために自分のC++のプログラムの依存関係についてきちんと理解しておく必要があったため、

  • C++のプログラムを書くときにつまづきやすい、依存関係について
  • CMakeを使ったC++のコンパイルについて

を解説しました。今回は、実際にC++の関数をCython化するコードを書き、前回のCMakeLists.txtを参考にsetup.pyを書き、Pythonから呼べるようにコンパイルします。

C++側のコードもおさらいとして載せます。

cpp_library/TestClass1.h
#include <gmp.h>

namespace my_library
{
class TestClass1{
    public:
        TestClass1();
        void test_function1();
        static void gmp_print_test();
};    
} // 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);
}

}

仮に、このようなtest.cppがあった場合、以下のCMakeLists.txtでコンパイルできることを確かめましょう。

cpp_library/test.cpp
#include "TestClass1.h"

using namespace my_library;

int main(){
    TestClass1::gmp_print_test();
}
cpp_library/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(TEST VERSION 1.1.0 LANGUAGES CXX)

# Executable will be in ../bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
set(CMAKE_CXX_FLAGS "-g -O0 -lgmp")


add_executable(test1
    test.cpp
    TestClass1.cpp
    )

target_sources(test1
    PRIVATE
)

target_include_directories(test1 PRIVATE /usr/local/gmp/6_1_2/include/)
target_link_libraries(test1 gmp /usr/local/gmp/6_1_2/lib)

とりあえずCython化

まず、cythonのコードを以下のように書いて、C++関数をラップします。
以前やったように、pxd ファイルはcythonのヘッダーファイル、pyxファイルはcythonのプログラム本体のファイルでした。
以下のようにC++をラップするように書きます。

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()
cython/test_class1.pxd
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()

cython/my_library.pxd
cdef extern from "../cpp_library/TestClass1.h" namespace "my_library":
    cdef cppclass TestClass1:
        TestClass1()
        void test_function1()
        void gmp_print_test()
cython/my_library.pyx
import cython
cimport cython

include "test_class1.pyx"

def test():
    return TestClass1Cython()

setup.py

依存するライブラリはsetup.pyでどのように書かれるのか。

ここから、setup.pyを、gmpライブラリに依存していることを明記して記述します。

実際、もうCMakeLists.txtの書き方がわかっていれば、簡単な作業です。
見ていただければわかりますが、

libraries=["gmp"],
library_dirs=["/usr/local/lib", "/usr/local/gmp/6_1_2/lib"],
include_dirs=["/usr/local/gmp/6_1_2/include"],

のように、

  • 依存するライブラリ
  • ライブラリのオブジェクトファイルの場所
  • ヘッダーファイルの場所

をCMakeLists.txtを書いた時のように書くだけです。

結果、以下のようなsetup.pyを作成しました。

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(
    "my_library", sources=["./cython/my_library.pyx",
           "./cpp_library/TestClass1.cpp"
                       ],
           libraries=["gmp"],
           library_dirs=["/usr/local/lib", "/usr/local/gmp/6_1_2/lib"],
           include_dirs=["/usr/local/gmp/6_1_2/include"],
           language="c++",
           extra_compile_args=['-std=c++1z',"-lgmp"]
    ),
    ]

setup(
    name = 'my_library',
    cmdclass = {'build_ext': build_ext},
    ext_modules = cythonize(ext_modules)
)


以上が終われば、無事にgmpライブラリに依存したC++のプログラムをcython化出来ています。

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.gmp_print_test()
print : 1 

すばらしい。。

まとめ

今回は、C++側のコードがなんらかのライブラリ(例では gmpライブラリ)に依存している時の、Cythonize のやり方について解説し、実際にコンパイルすることができました。

次は、C++側のクラスオブジェクトを、Pythonのクラスオブジェクトに渡すやり方について解説していきます。
自分的には、この渡し方までわかれば大抵のことが出来てくると思うので、是非ご覧ください!!

参考になった、という方がおりましたら、ぜひLGTM、コメント、お待ちしております。

今回はこの辺で。

おわり。

kenmaro
eaglys
"EAGLYSは、未だ活用しきれていない企業に眠るデータ資産を、 安全にデータ分析・AI構築・運用するサポートを行っています。"
https://eaglys.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした