Search code examples
pythonc++pybind11

Eigen::Matrix contained inside a struct is marked as non-writeable by pybind11


I'm using pybind11 to create a Python binding for my C++ library that uses Eigen types. However, when I try to bind a struct that contains an Eigen::Matrix, the corresponding generated Python class has all the ndarray (the Python destination class for Eigen::Matrix members) elements with the Writeable flag set to false, which are therefore non-modifiable. I've managed to reduce my problem to the following MWE:

// binding.cpp
#include "Eigen/Dense"
#include "pybind11/pybind11.h"
#include "pybind11/eigen.h"

struct Example {
   Eigen::Matrix<double, 3, 1> m;
};

namespace py = pybind11;
PYBIND11_MODULE(MyBinding, m)
{
   py::class_<Example>(m, "Example")
      .def(py::init<>())
      .def_readwrite("m", &Example::m);
}
# script.py
import MyBinding
ex = MyBinding.Example()
# ex.m has Writeable flag equal to false!

On the contrary, when binding the following code, everything works as expected, i.e. the value returned by f, when called in Python, is modifiable (has the Writeable flag set to true):

Eigen::Matrix<double,3,1> f() {
   return {};
}

PYBIND11_MODULE(MyBinding, m)
{
   m.def("f", &f);
}

What am I missing here? Thank you for any help!


Solution

  • def_readwrite(⋯) is a shortcut for def_property(⋯) (source link) where the getter takes the class instance by const-reference. Therefore the resulting numpy array is not modifiable.

    All you need to do is to write custom getter and setter functions:

    namespace py = pybind11;
    
    PYBIND11_MODULE( MyBinding, m )
    {
        py::class_<Example>( m, "Example" )
            .def(py::init<>())
            .def_property( "m",
                // Getter
                []( Example const& self )
                {
                    return self.m;
                },
                // Setter
                []( Example& self, Eigen::Matrix<double,3,1> const& val )
                {
                    self.m = val;
                }
            )
        ;
    }
    

    Caution: untested code, you may need to pass an extra return-value policy argument