PythonからC/C++を呼び出したい
などの理由から、C/C++をPythonから呼び出したいということがある。そのような場合に使える手法について書いておく。
1.Python/C API
C/C++側で#include<Python.h>し、仲立ちとして機能するファイルを作ることでC/C++とPythonのインターフェースとして機能する。 しかし、オーバーヘッドがひどいらしいのと書き方が後述のCythonに比べ難解であるため割愛する(暇があったら書くかも)。詳しくは公式ドキュメントを参照してほしい。
1. C や C++ による Python の拡張 — Python 3.8.3 ドキュメント
2.Cython
CythonはPythonとC/C++の仲立ちとして機能するDSLのようなものだ。サンプルコードを見て頂ければ分かるがほどんどPythonと書き方は同じである。
- sample.pyx
import cython def func1(int x): cdef: int i i=5 i+=x return i cdef func2(int y): return y*2 cpdef func3(int z): return func2(z)
pyxファイルはCythonのソースファイルである。Pythonと変わらないように見えるがcdefやcpdefといった特殊な宣言が使える。仮引数の型指定はしなくてもエラーにならないが、指定したほうが速い(要検証)。 cdefやcpdefといったPythonで見覚えのない表現があるが、これらはCython特有の予約語であり、以下のような意味を持つ。
宣言 | Pythonからの参照 | Cythonからの参照 | 速度 |
---|---|---|---|
def | 可 | 可 | 遅い |
cdef | 不可 | 可 | 速い |
cpdef | 可 | 可 | Cythonからの参照は早く、Pythonからの場合は遅い |
cimport
はC/C++のライブラリを.pyxファイルにimport
する。vector<>型を扱わなければならない時に使う。
from libcpp.vector cimport vector
- setup.py
from setuptools import setup, Extension,find_packages from Cython.Build import cythonize ext = Extension("sample", sources=["sample.pyx"],language="c++") setup(name="sample", ext_modules=cythonize([ext]))
setup.pyは特殊なPythonプログラムであり、Pythonプログラムをパッケージ化し、pipでインストールできる形にできる。 setup.pyにはいくつか属性を指定するが、そのうち今回注目するのはext_modulesという属性だ。ext_modulesは拡張モジュールといい、Python以外で書かれたモジュールのことを指す。 ext_moduleにはリストで拡張モジュールを与える必要がある。 * include 拡張モジュールが必要とするヘッダファイル * libraries 拡張モジュールが依存するライブラリ * library_dirs 非標準のパスにそのライブラリがある場合に指定
sourceに指定したファイルはコンパイルを行う。pyxもコンパイルされるため、cdef宣言でcppファイルからincludeしている関数のあるcppファイルをsourceにしていると多重includeを起こす。
ext = Extension("sample", sources=["sample.pyx"],language="c++")
ここで拡張モジュールを作成している。sampleという名前の拡張モジュールをsample.pyxをもとに作るということである。language="c++"
を指定すると、C++をサポートしてくれる。
モジュール名は特別な意図がない限り、モジュール全体の名前と同じにしておいたほうがよい。
これらのプログラムが完成したら、setup.pyがあるディレクトリで以下のコマンドを実行する。するとsampleモジュールがシステム内でimportできるようになる。
python setup.py install
- 実行結果
>>> import sample >>> sample.func1(0) 5 >>> sample.func2(5) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'sample' has no attribute 'func2' >>> sample.func3(5) 10 >>>
cdef
で宣言された関数はPythonからの呼び出しに失敗していることが分かる。
クラス型を扱いたい場合そのクラス型で使われている型全て合わせて定義を取得する必要がある。メソッドにおいてC/C++のクラス型を引数に取る必要がある場合、Cythonで宣言したクラスでラップする。これを
cdef class cy_hoge: cdef Hoge* ptr def __cinit__(self,Hoge hoge): self.ptr = hoge def __deadaloc(self): del ptr
のように指定する。コンストラクタの引数に型指定をしなければ、Pythonの型であるとみなされエラーを起こす。呼び出し元クラスで引数用に変数宣言する場合でも型宣言は必須だ。
Cythonでポインタを扱う
CythonではC++オブジェクトを扱う場合に、ポインタで扱うのが有効である。ヒープにオブジェクトを確保するために、new演算子を使うことも可能である。 ポインタをオブジェクトに解決するために、アドレス解決演算子*は[0]、アドレス演算子&は&のままCythonで使える。
Cython
cdef extern from "any-path" namespace "any-namespace"