关键词:c++,python,eigen
1. 环境
- python3.6
- g++7.5
- eigen3
2. 拓展
stub.cc
#define PY_SSIZE_T_CLEAN
#include <Python.h>
extern PyObject* initModule();
PyMODINIT_FUNC
PyInit_matrix(void) // python import时的入口函数
{
return initModule();
}
python_matrix.cc
#include <Python.h>
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
typedef struct
{
PyObject_HEAD
MatrixXd *matrix=nullptr;
} PyMatrixObject;
// 对应 python 中类创建时的 __new__
static PyObject *
PyMatrix_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
std::cout << type << std::endl;
PyMatrixObject *self;
self = (PyMatrixObject *)(type->tp_alloc(type, 0)); // 构造对象,变量;并指定self->ob_type = type
char *kwlist[] = {"width", "height", NULL};
int width = 0;
int height = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, // 可以采用关键字或位置进行参数
&width, &height))
{
Py_DECREF(self);
return NULL;
}
if (width <= 0 or height <= 0)
{
PyErr_SetString(PyExc_ValueError, "The height and width must be greater than 0.");
return NULL;
}
self->matrix = new MatrixXd(width, height); // 为数据部分的矩阵申请内存
return (PyObject *)self;
}
// 自动调用
static void
*PyMatrix_dealloc(PyObject *obj)
{
delete ((PyMatrixObject *)obj)->matrix; // 删除 eigen 矩阵申请的内存
Py_TYPE(obj)->tp_free(obj); // 释放变量/对象,obj是内存的初始位置
}
// 返回 PyMatrixObject 对象的 矩阵部分
inline MatrixXd *ParseMatrix(PyObject *obj){
return ((PyMatrixObject *)obj)->matrix;
}
inline PyObject *ReturnMatrix(MatrixXd *m, PyTypeObject *type){
PyMatrixObject *obj = PyObject_NEW(PyMatrixObject, type);
obj->matrix = m;
return (PyObject *)obj;
}
// 矩阵加法
static PyObject *
PyMatrix_add(PyObject *a, PyObject *b)
{
MatrixXd *matrix_a = ParseMatrix(a);
MatrixXd *matrix_b = ParseMatrix(b);
if (matrix_a->cols() != matrix_b->cols() or matrix_a->rows() != matrix_b->rows()){
PyErr_SetString(PyExc_ValueError, "The input matrix must be the same shape.");
return NULL;
}
MatrixXd *matrix_c = new MatrixXd(matrix_a->cols(), matrix_b->rows()); // 为结果声明内存
*matrix_c = *matrix_a + *matrix_b; // 计算结果
std::cout << a->ob_type << std::endl;
return ReturnMatrix(matrix_c, a->ob_type);
}
static PyObject *
PyMatrix_minus(PyObject *a, PyObject *b)
{
MatrixXd *matrix_a = ParseMatrix(a);
MatrixXd *matrix_b = ParseMatrix(b);
if (matrix_a->cols() != matrix_b->cols() or matrix_a->rows() != matrix_b->rows()){
PyErr_SetString(PyExc_ValueError, "The input matrix must be the same shape.");
return NULL;
}
MatrixXd *matrix_c = new MatrixXd(matrix_a->cols(), matrix_b->rows());
*matrix_c = *matrix_a + *matrix_b;
return ReturnMatrix(matrix_c, a->ob_type);
}
static PyObject *
PyMatrix_multiply(PyObject *a, PyObject *b)
{
MatrixXd *matrix_a = ParseMatrix(a);
MatrixXd *matrix_b = ParseMatrix(b);
if (matrix_a->cols() != matrix_b->rows()){
PyErr_SetString(PyExc_ValueError, "The colonm rank of matrix A must be the same as the row rank of matrix B.");
return NULL;
}
MatrixXd *matrix_c = new MatrixXd(matrix_a->rows(), matrix_b->cols());
*matrix_c = (*matrix_a) * (*matrix_b);
return ReturnMatrix(matrix_c, a->ob_type);
}
static PyObject *PyMatrix_str(PyObject *a)
{
MatrixXd *matrix = ParseMatrix(a);
std::stringstream ss;
ss << *matrix;
return Py_BuildValue("s", ss.str().c_str());
}
static PyNumberMethods numberMethods = {
PyMatrix_add, //nb_add
PyMatrix_minus, //nb_subtract;
PyMatrix_multiply, //nb_multiply
nullptr, //nb_remainder;
nullptr, //nb_divmod;
nullptr, // nb_power;
nullptr, // nb_negative;
nullptr, // nb_positive;
nullptr, // nb_absolute;
nullptr, // nb_bool;
nullptr, // nb_invert;
nullptr, // nb_lshift;
nullptr, // nb_rshift;
nullptr, // nb_and;
nullptr, // nb_xor;
nullptr, // nb_or;
nullptr, // nb_int;
nullptr, // nb_reserved;
nullptr, // nb_float;
nullptr, // nb_inplace_add;
nullptr, // nb_inplace_subtract;
nullptr, // nb_inplace_multiply;
nullptr, // nb_inplace_remainder;
nullptr, // nb_inplace_power;
nullptr, // nb_inplace_lshift;
nullptr, // nb_inplace_rshift;
nullptr, // nb_inplace_and;
nullptr, // nb_inplace_xor;
nullptr, // nb_inplace_or;
nullptr, // nb_floor_divide;
nullptr, // nb_true_divide;
nullptr, // nb_inplace_floor_divide;
nullptr, // nb_inplace_true_divide;
nullptr, // nb_index;
nullptr, //nb_matrix_multiply;
nullptr //nb_inplace_matrix_multiply;
};
PyObject *PyMatrix_data(PyObject *self, void *closure)
{
PyMatrixObject *obj = (PyMatrixObject *)self;
Py_ssize_t width = obj->matrix->cols();
Py_ssize_t height = obj->matrix->rows();
PyObject *list = PyList_New(height);
for (int i = 0; i < height; i++)
{
PyObject *internal = PyList_New(width);
for (int j = 0; j < width; j++)
{
PyObject *value = PyFloat_FromDouble((*obj->matrix)(i, j));
PyList_SetItem(internal, j, value);
}
PyList_SetItem(list, i, internal);
}
return list;
}
PyObject *PyMatrix_rows(PyObject *self, void *closure)
{
PyMatrixObject *obj = (PyMatrixObject *)self;
return Py_BuildValue("i", obj->matrix->rows());
}
PyObject *PyMatrix_cols(PyObject *self, void *closure)
{
PyMatrixObject *obj = (PyMatrixObject *)self;
return Py_BuildValue("i", obj->matrix->cols());
}
static PyGetSetDef MatrixGetSet[] = {
{"data", (getter)PyMatrix_data, nullptr, nullptr},
{"row", (getter)PyMatrix_rows, nullptr, nullptr},
{"colunm", (getter)PyMatrix_cols, nullptr, nullptr},
{nullptr}};
PyObject *PyMatrix_tolist(PyObject *self, PyObject *args)
{
return PyMatrix_data(self, nullptr);
}
static PyMethodDef MatrixMethods[] = {
{"to_list", (PyCFunction)PyMatrix_tolist, METH_VARARGS, "Return the matrix data to a list object."},
{nullptr}};
static PyTypeObject MatrixType = {
PyVarObject_HEAD_INIT(nullptr, 0) "matrix.Matrix", /* tp_name */
sizeof(PyMatrixObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PyMatrix_dealloc, /* tp_dealloc */
nullptr, /* tp_print */
nullptr, /* tp_getattr */
nullptr, /* tp_setattr */
nullptr, /* tp_reserved */
nullptr, /* tp_repr */
&numberMethods, /* tp_as_number */
nullptr, /* tp_as_sequence */
nullptr, /* tp_as_mapping */
nullptr, /* tp_hash */
nullptr, /* tp_call */
PyMatrix_str, /* tp_str */
nullptr, /* tp_getattro */
nullptr, /* tp_setattro */
nullptr, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
"Coustom matrix class.", /* tp_doc */
nullptr, /* tp_traverse */
nullptr, /* tp_clear */
nullptr, /* tp_richcompare */
0, /* tp_weaklistoffset */
nullptr, /* tp_iter */
nullptr, /* tp_iternext */
MatrixMethods, /* tp_methods */
nullptr, /* tp_members */
MatrixGetSet, /* tp_getset */
nullptr, /* tp_base */
nullptr, /* tp_dict */
nullptr, /* tp_descr_get */
nullptr, /* tp_descr_set */
0, /* tp_dictoffset */
nullptr, /* tp_init */
nullptr, /* tp_alloc */
PyMatrix_new /* tp_new */
};
static PyObject *PyMatrix_ones(PyObject *self, PyObject *args, PyObject *kwargs)
{
PyMatrixObject *m = (PyMatrixObject *)PyMatrix_new(&MatrixType, args, kwargs);
m->matrix->setOnes();
return (PyObject *)m;
}
static PyObject *PyMatrix_zeros(PyObject *self, PyObject *args, PyObject *kwargs)
{
PyMatrixObject *m = (PyMatrixObject *)PyMatrix_new(&MatrixType, args, kwargs);
m->matrix->setZero();
return (PyObject *)m;
}
static PyObject *PyMatrix_random(PyObject *self, PyObject *args, PyObject *kwargs) // self,没有使用
{
PyMatrixObject *m = (PyMatrixObject *)PyMatrix_new(&MatrixType, args, kwargs);
m->matrix->setRandom();
return (PyObject *)m;
}
static PyObject *PyMatrix_matrix(PyObject *self, PyObject *args)
{
PyObject *data = nullptr;
if (!PyArg_ParseTuple(args, "O", &data))
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. 1");
return nullptr;
}
if (!PyList_Check(data))
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. 2");
return nullptr;
}
int height = PyList_GET_SIZE(data);
if (height <= 0)
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. 2");
return nullptr;
}
PyObject *list = PyList_GET_ITEM(data, 0);
if (!PyList_Check(list))
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. 3");
return nullptr;
}
int width = PyList_GET_SIZE(list);
MatrixXd *p_mat = new MatrixXd(width, height);
for (int i = 0; i < height; i++)
{
PyObject *list = PyList_GET_ITEM(data, i);
if (!PyList_Check(list))
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. 3");
return nullptr;
}
int tmp = PyList_GET_SIZE(list);
if (width != tmp)
{
PyErr_SetString(PyExc_ValueError, "Please pass a 2 dimensions list object. Each elements of it must be the same length.");
return nullptr;
}
width = tmp;
for (int j = 0; j < width; j++)
{
PyObject *num = PyList_GET_ITEM(list, j);
if (!PyFloat_Check(num))
{
PyErr_SetString(PyExc_ValueError, "Every elements of the matrix must float.");
return nullptr;
}
(*p_mat)(i, j) = ((PyFloatObject *)num)->ob_fval;
}
}
return ReturnMatrix(p_mat, &MatrixType);
}
static PyMethodDef matrixMethods[] = { // module中的方法
{"ones", (PyCFunction)PyMatrix_ones, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with initial values one."},
{"zeros", (PyCFunction)PyMatrix_zeros, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with initial values zero."},
{"random", (PyCFunction)PyMatrix_random, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with random values"},
{"matrix", (PyCFunction)PyMatrix_matrix, METH_VARARGS, "Return a new matrix with given values"},
{nullptr}};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"matrix",
"Python interface for Matrix calculation",
-1,
matrixMethods};
PyObject *initModule(void)
{
PyObject *m;
if (PyType_Ready(&MatrixType) < 0) // 完善 tp_alloc
return NULL;
m = PyModule_Create(&module); // 创建module
if (m == NULL)
return NULL;
Py_INCREF(&MatrixType);
std::cout << &MatrixType << std::endl; // 所有 PyMatrixObject 对象的 ob_type 都指向这里
if (PyModule_AddObject(m, "Matrix", (PyObject *)&MatrixType) < 0) // 创建module中的object
{
Py_DECREF(&MatrixType);
Py_DECREF(m);
return NULL;
}
return m;
}
/*
g++ -shared -fPIC -I/usr/include/python3.6 -I/usr/include/eigen3 stub.cc python_matrix.cc -o matrix.so
*/
详细看相关代码的注释
3. other
3.1 ReturnMatrix(matrix_c, a->ob_type) 中 a->ob_type 是什么?在哪里赋值初始化的。
猜测是在 PyMatrix_new 中,声明变量时 PyMatrixObject *self; self = (PyMatrixObject *)(type->tp_alloc(type, 0)); // 构造对象,变量;并指定self->ob_type = type
赋值给 self->ob_type 的。
这样的话,那所有的 PyMatrixObject.ob_type 应该都是一样的。所以在上面的代码中加了几行 std::cout。编译之后,在python中测试时的结果:
>>> import matrix
0x7fcbec5af120
>>> a = matrix.random(2,3)
0x7fcbec5af120
>>> b = matrix.random(2,3)
0x7fcbec5af120
>>> c = a+b
0x7fcbec5af120
>>>
可以看到 a,b,c 三个 PyMatrixObject 的 ob_type 都指向相同的位置。
3.2 对象的函数&module的函数
二者的声明是一样的,都是一个static PyMethodDef
对象
二者的参数是不同的,对象的函数第一个参数为自己。PyObject *PyMatrix_data(PyObject *self, void *closure)
二者返回的结果一般也不一样,module的function 一般返回一个 pyobject,对象的函数一般返回int,string,list等python对象。
对象的函数
static PyMethodDef MatrixMethods[] = {
{"to_list", (PyCFunction)PyMatrix_tolist, METH_VARARGS, "Return the matrix data to a list object."},
{nullptr}};
module的函数
static PyMethodDef matrixMethods[] = { // module中的方法
{"ones", (PyCFunction)PyMatrix_ones, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with initial values one."},
{"zeros", (PyCFunction)PyMatrix_zeros, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with initial values zero."},
{"random", (PyCFunction)PyMatrix_random, METH_VARARGS | METH_KEYWORDS, "Return a new matrix with random values"},
{"matrix", (PyCFunction)PyMatrix_matrix, METH_VARARGS, "Return a new matrix with given values"},
{nullptr}};