C/C++ のデータを np.array として Python から見る。
struct Particle {
float x[3], v[3];
};
vector<Particle> v;
や Particle*
のようなデータが C/C++ 側にあるとする。
numpy.array from C/C++
まずは numpy/arrayobject.h
をインクルードして、初期化する。これは static 変数を初期化しているようで、ソースコードが複数ある場合は numpy を使う全てのソースコードで import_array()
を呼ばないといけない。さもないと、segmentation fault になる。
# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
# include "numpy/arrayobject.h"
PyMODINIT_FUNC
PyInit_cext07(void) {
import_array();
return PyModule_Create(&module);
}
NumPy のヘッダを読むために setup.py
では include_dirs
を設定する。
import numpy as np
from distutils.core import setup, Extension
setup(name='cext07',
version='0.0.1',
description='c_ext07 read data as np.array',
author='Jun Koda',
url='https://github.com/junkoda/python_c_ext',
ext_modules=[
Extension('cext07', ['cext07.c'],
include_dirs = [np.get_include()]),
],
)
データのありかをPythonに
Python側から参照できるように vector<Particle>
のポインタを渡す関数:
static void py_particles_free(PyObject *obj);
static PyObject* py_particles_alloc(PyObject *self, PyObject *args)
{
// Allocate a new C++ pointer as a Python object "_Particles"
vector<Particle>* const p= new vector<Particle>();
p->resize(10); // 仮にデータは粒子が10個
return PyCapsule_New(p, "_Particles", py_particles_free);
}
void py_particles_free(PyObject *obj)
{
// Destructor function for _Particles pointer, called automatically from Python
vector<Particle>* const p=
(vector<Particle>*) PyCapsule_GetPointer(obj, "_Particles");
delete p;
}
データを np.array として Python に渡す
vector<Particle>
を np.array
として渡す関数:
PyObject* py_as_array(PyObject *self, PyObject *args)
{
PyObject* py_particles;
if(!PyArg_ParseTuple(args, "O", &py_particles))
return NULL;
// Get vector<Particle>* pointer back from _Particles
vector<Particle>* const p=
(vector<Particle>*) PyCapsule_GetPointer(py_particles, "_Particles");
if(!p) return NULL;
int nd=2; // 行と列がある2次元 array
int ncol= sizeof(Particle)/sizeof(float); // ncol=6 x[3], v[3] の6列
npy_intp dims[]= {(npy_intp) p->size(), ncol};
return PyArray_SimpleNewFromData(nd, dims, NPY_FLOAT, &(p->front()));
}
いつものC/C++拡張の関数
static PyMethodDef methods[] = {
{"_particles_alloc", py_particles_alloc, METH_VARARGS, "new Particles"},
{"_as_array", py_as_array, METH_VARARGS, "vector<Particle> as an array"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"cext07", // name of this module
"Use vector<Particle>", // Doc String
-1,
methods
};
Pythonのクラスでラッピングする
import numpy as np
import cext07
class Particles:
def __init__(self):
self._p= cext07._particles_alloc() # _Particle ポインタ
def __repr__(self):
return cext07._as_array(self._p).__repr__()
def __getitem__(self, index):
return cext07._as_array(self._p)[index]
def asarray(self):
return cext07._as_array(self._p)
#などなど
import cext07
p = Particles()
p # __repr__ により array が見られる
p[0] # __getitem__; 最初の粒子
p[2:5,2] # slice も np.array が扱ってくれる
p[:,0] # x[0]
p[:,4] # v[1]
p[:] # 全データ
追記
この例なら、Python のクラスは不要で、as_array関数だけでよかった。あとで簡略化します。
全コード
全コードは GitHub にあります。