コンテンツへスキップ

Cythonの使い方メモ

タグ:

Cython( https://cython.org/ ) は、Python言語の体裁と保った状態でC言語との親和性を高めるためのモジュールです。決められた条件に従って記述することで、Pythonの記述がCに置き換わり実行されます。

Cに置き換わることで速くなる場合もありますが、静的解決に置き換えることで動作の効率化を図るといった手法となります。

ProtocolBuffersのC++版をPythonから使用する

Cythonの便利な機能として、C/C++で定義された機能を変換インターフェースを介して利用できる事です。

というわけで、試しにProtocolBuffersのC++版定義をPythonから呼び出してみます。

demo.proto

syntax = "proto3";

package demo;

message Vector3 {
    float x = 1;
    float y = 2;
    float z = 3;
}

message Param {
    int32 n_value = 1;
    string s_value = 2;
}

message DemoMessage {
    int32 n_param = 1;
    float f_param = 2;
    Vector3 vct = 3;
    repeated Param params = 4;
}

定義を作成したら、protocを使用してC++コードを生成します。

以下の様に実行すると、 demo.pb.h, demo.pb.cc というファイルが生成されます。

$ protoc --cpp_out=. demo.proto

Cythonが導入されていてもさすがにこのままでは使用できません。Pythonから利用するにはpydファイルを作成する必要があります。

pydファイルはC++の定義をPythonから使用するための変換定義を記述するためのものです。

test.pxd

詳しい使用方法は以下に記載があります。

https://cython.readthedocs.io/en/stable/src/userguide/wrapping_CPlusPlus.html

# distutils: language=c++

# std::string に対応させる場合に必要
from libcpp.string cimport string

# このコードが必要であることを認識させる
cdef extern from "demo.pb.cc" namespace "demo":
    pass

# ProtocolBuffersの定義を記載
cdef extern from "demo.pb.h" namespace "demo":
    cdef cppclass Vector3:
        Vector3() except +

        float x() const
        void set_x(float x)
        float y() const
        void set_y(float y)
        float z() const
        void set_z(float z)

    cdef cppclass Param:
        Param() except +

        int n_value() const
        void set_n_value(int v)
        bint has_s_value() const
        void clear_s_value() const
        const string& s_value() const
        void set_s_value(string& s)

    cdef cppclass DemoMessage:
        DemoMessage() except +

        int n_param() const
        void set_n_param(int v)
        float f_param() const
        void set_f_param(float v)

        # vct
        const Vector3* vct() const
        Vector3* mutable_vct()

        # params
        int params_size() const
        void clear_params()
        # const Param& params(int index) const
        Param* mutable_params(int index)
        Param* add_params()

        bint SerializeToString(string* output) const
        bint ParseFromString(const string data)

        string DebugString() const

test.pyx

# distutils: language=c++

def test_add(int v1, int v2):
    return v1 + v2

def test_sub(int v1, int v2):
    return v1 - v2


def func_vector():

    cdef DemoMessage m1

    m1.set_n_param(123)
    m1.set_f_param(1.0)

    v = m1.mutable_vct()
    v.set_x(1)
    v.set_y(3)
    v.set_z(5)

    m1.clear_params()
    for n in range(32):
        p = m1.add_params()
        p.set_n_value(n)
        p.set_s_value("s".encode("utf-8"))

    cdef string output

    m1.SerializeToString(&output)

    cdef DemoMessage m2

    m2.ParseFromString(output)
    for n in range(m2.params_size()):

        p = m2.mutable_params(n)
        print(p.n_value(), p.s_value())

    print(m2.DebugString())

pydで定義したものを実際に使用してみます。

pxdファイルとpyxファイルからモジュールを作成

pxdファイルやpyxファイルはそのままではPythonが理解出来ません。理解できる様にするために setup.py を使用してモジュールに変換します。

from __future__ import annotations
from distutils.core import setup, Extension
from Cython.Build import cythonize

ext = Extension(
    "test",
    sources=["test.pyx"],
    include_dirs=["."],
    extra_compile_args=["-std=c++11"],
    libraries=["protobuf"],
)
setup(name="test", ext_modules=cythonize([ext], annotate=True))
$ ython setup.py build_ext --inplace

利用するには作成したモジュールを読み込んでやるだけです。

import test


def main():

    v = test.test_add(1, 1)
    print(v)
    v = test.test_sub(1, 1)
    print(v)

    test.func_vector()


if __name__ == "__main__":
    main()