やりたいこと
Pythonのnumpy配列をCの関数に渡したい.今回は参照渡しと値渡しの両方を実装する.
・参照渡し
受け取り側で中身をいじると渡した側にも反映される
・値渡し
受け取り側でいじっても渡した側に反映されない
値渡しと言っても,参照渡しで受け取った配列の中身を新たに作った配列にコピーするだけなので,やることは参照渡しとあまり変わらない.
Pythonからの呼び出し例
c_moduleというCの関数を扱うモジュールを作成する.
inc_array: numpy配列を参照渡しで受け取り,各要素に同じ値を加えて返す.
not_inc_array: numpy配列を値渡しのような形で受け取り,それを操作する.
なお配列は2次元,8バイト浮動小数点数の想定.次元数に関するエラーチェックはここでは入れていないが本当はきちんと入れないといけない.
このように,inc_arrayでは元の配列に変更が反映されるが,not_inc_arrayでは反映されない.
Cの関数
func.c
Cのラッパー
wrap.c
コンパイル
解説: ヘッダー
Pythonと
Cとの連携では
Pythonのオブジェクトを
PyObjectという型で扱うので,そのために
Python.hを
includeする必要があった.
今回は
numpyの配列
numpy.ndarrayを扱うので,
arrayobject.hというヘッダーファイルを
includeする必要がある.
fatal error: numpy/arrayobject.h: No such file or directory
ヘッダーファイルが見つからないというエラーが出ることがある.
numpyを
pipでインストールしているとこうなるらしい.一度アンインストールして
aptで再インストールすると,このヘッダーファイルも付いてくる模様.
参照:
numpyにarrayobject.hが存在しないのでpandasがインストールできない · Issue #1 · philopon/py3-rdkit
それからパッケージ名は
python-numpyではダメで,
python3-numpyだとインストールできた(自分の場合).
参照:
numpy_install - HAIK(QHM),オープンソース活用と実験
管理者権限が無くて
aptが使えない場合はソースコードなどからヘッダーファイルを落としてくる必要があると思われる.
1行目の
#define ...は,非推奨の機能をオフにするもの.これが無いとコンパイル時に以下のような警告文が出る.
非推奨の機能というのは,例えば
PyArrayObject(
numpy配列の型)の持っているクラスを直接扱う機能など.
解説: 参照渡し
numpy配列をCのポインタ配列として受け取る
ソースコード内の順番とは前後するが,まずはベースとなる関数について.
PyArray_TYPE( PyArrayObject *obj )
objのデータ型を取得する.今回は
8バイト浮動小数点数と想定しているので,そうでない場合はエラーを返す.
PyArray_DATA( PyArrayObject *obj )
objのデータを指示するポインタを取得する.
この関数において変数
cは変数
arrayinと同じメモリ領域を参照している.これを戻り値として返す.
受け取った配列の中身を操作する
PyArrayObject型の
arrayinを
getptr_to_pyarray_doubleで
double型ポインタ配列に変換したものが変数
c.
これの各要素に
incを足す.
Pythonからの呼び出し用のラッパー
PyObject PyArray_FROM_OTF(PyObject *obj, int typenum, int requirements)
objを
ndarrayに変換する関数.その機能は
PyArray_FROM_Oで提供されているが,そこに型の指定などの機能が加わったのがこの関数とのこと.
参照:
https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_FROM_OTF
https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_FROM_O
Py_DECREF(PyObject *obj)
とメモリ開放をする手続き.
自身が他のいくつの変数から参照されているかを確認して,それがゼロならメモリを解放する,というものらしい.
参照:
Pythonのオブジェクトへの参照とガベージコレクション #ポエム - Qiita
https://docs.python.org/ja/3.10/c-api/refcounting.html#c.Py_DECREF
Pythonから呼び出してみる
最初の例で示したように,配列
aの各要素に
1.1が足されており,
C側での
aに対する操作が
Python側にそのまま返ってきていることが確認できる.
解説: 値渡し的な受け取り
numpy配列の中身をCのポインタ配列にコピーする
先の関数getptr_to_pyarray_doubleでnumpy配列をポインタ配列として受け取り,その中身を別途定義した配列にコピーする.
この関数も次元数に関する制限は不要なのだが,ここでは2次元という前提で大きさを取得している.
受け取った配列の中身を操作する
py_not_inc_arrayはpy_inc_arrayとほぼ同じ.呼び出す関数がinc_arrayからnot_inc_arrayに変わるだけ.
Pythonから呼び出してみる
今度は関数の呼び出しの前後でndarrayの中身が変わっていないことが確認できる.
解説: コンパイル
「はじめに」でも述べたように,
Cで
Pythonオブジェクト及び
numpy配列を使うのに必要な各ヘッダーファイル
Python.h及び
arrayobject.hがあるディレクトリを検索対象に含める必要がある(ここで
arrayobject.hは
numpyというディレクトリ内にある前提で,
numpy/arrayobject.hで指定).
自分の環境では
Python.hは
/usr/include/python3.9に,
arrayobject.hは
/usr/include/python3.9/numpyにあるので,
-Iで
/usr/include/python3.9を加えている.