Python C/C++ 拡張パターン - データを np.array として Python に渡す



C/C++ のデータを np.array として Python から見る。

```cpp
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 になる。

```cpp
#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` を設定する。

```python
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>` のポインタを渡す関数：

```cpp
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` として渡す関数： 

```cpp
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; // 行と列がある２次元 array
  int ncol= sizeof(Particle)/sizeof(float); // ncol=6 x[3], v[3] の６列
  npy_intp dims[]= {(npy_intp) p->size(), ncol};

  return PyArray_SimpleNewFromData(nd, dims, NPY_FLOAT, &(p->front()));
}
```

いつものC/C++拡張の関数

```cpp
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のクラスでラッピングする

```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)        
    #などなど

```

```python
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](https://github.com/junkoda/python_c_ext/tree/master/07_container) にあります。