暗号勉強会

あまりコードが出てこない話

PythonからC/C++を呼び出したい

  • C/C++で書いた関数、クラスをPythonから再利用したい。
  • Pythonを高速化したい。

などの理由から、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はPythonC/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からの場合は遅い

cimportC/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"

追記中