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()