pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

[QUESTION] How can I overload the [ ] operator?

Open jeanettejohnson opened this issue 4 years ago • 10 comments

I understand how the other operators are overloaded but I'm having difficulty with this one. This is my code (simplified):

float& Vec::operator[](unsigned i)
{
    return privatedata[i];
}

py::class_<Vec>(m, "Vec")
      .def(py::init<unsigned &>())
      .def(unsigned() [] float());

It may not come as a surprise that this does not work. Since a Vec object is not passed in or out of the operator directly, my binding cannot contain py::self. Please let me know what I'm doing wrong! Thanks everyone.

jeanettejohnson avatar Jul 01 '21 20:07 jeanettejohnson

found this, gonna try it: https://github.com/pybind/pybind11/issues/2702

EDIT: no luck, but my binding is now:

.def("__getitem__", (float (Vec::*)(unsigned)) &Vec::operator[]);

jeanettejohnson avatar Jul 01 '21 21:07 jeanettejohnson

Does this work?

py::class_<Vec>(m, "Vec")
        .def(py::init<unsigned &>())
        .def("__setitem__", [](Vec* vec, unsigned index, float val) { (*vec)[index] = val; })
        .def("__getitem__", &Vec::operator[]);

yasamoka avatar Jul 04 '21 15:07 yasamoka

Thank you for the reply! I tried that, and it resulted in this compilation error:

  src/bindings.cpp:158:10: error: no matching member function for call to 'def'
          .def("__getitem__", &Vec::operator[]);
          ~^~~

It seemed to be fine with the setitem binding, though.

jeanettejohnson avatar Jul 06 '21 18:07 jeanettejohnson

Can you show me the full code snippet?

yasamoka avatar Jul 06 '21 19:07 yasamoka

Yes!

Binding:

    py::class_<Vec>(m, "Vec")
        .def(py::init<unsigned &>())
        .def("size", &Vec::size)
        .def("__setitem__", [](Vec* vec, unsigned index, float val) { (*vec)[index] = val; })
        .def("__getitem__", &Vec::operator[]);

C++:

float Vec::operator[](unsigned i) const
{
    GAPS_ASSERT(i < mSize);
    return mData[i];
}

How I'm accessing it in Python (probably not relevant since I've never gotten it to compile, ha ha:

ax.plot(np.array(range(1, nsamples+1)), np.array(vector[i]), label="Data " + str(i))

Where vector is a Vec object. (Calls to size(), etc. work fine)

jeanettejohnson avatar Jul 06 '21 19:07 jeanettejohnson

That's strange. Can you provide a minimal version of your source code files and push them somewhere so I can try to compile and see what's up?

yasamoka avatar Jul 06 '21 19:07 yasamoka

The minimal version of the source code would be great, as @yasamoka mentioned.

However I assumed parts of your code:

#include "pybind11/pybind11.h"
#include <vector>

namespace py = pybind11;

class Vec
{
public:
    Vec(unsigned int m_size) : mData(m_size, 0)
    {
        mData.reserve(m_size);
    }

    unsigned int size() const
    {
        return mData.size();
    }

    float* operator[](unsigned i)
    {
        return &mData[i];
    }

private:
    std::vector<float> mData;
};

PYBIND11_MODULE(mymodule, m)
{
    py::class_<Vec>(m, "Vec")
        .def(py::init<unsigned &>())
        .def("size", &Vec::size)
        .def("__setitem__", [](Vec &self, unsigned index, float val)
             { *self[index] = val; })
        .def("__getitem__", [](Vec &self, unsigned index)
             { return self[index]; });
}

I tested it out for small array and assign some floats and print them. @jeanettejohnson is this working for you?

illumitata avatar Jul 06 '21 23:07 illumitata

I like the proposed solution. However, you might want to be a little bit more protective to your python clients. In python when you return:

  float* operator[](unsigned i)
  {
      return &mData[i];
  }

a pointer in mData might be out of bounds - possibly corrupting data or crashing the python interpreter - when i > len(Vec). You would protect your clients better when you would change the above to:

  float* operator[](unsigned i)
  {
      return &(mData.at(i));
  }

Or do something similar in the PYBIND11_MODULE. The at() function will throw an out_of_range error, which will be caught by pybind11 en turned into a pythonic IndexError.

maartenuni avatar Jun 15 '22 10:06 maartenuni

How would one extend this answer to deal with slicing as well? (Such that Vec[5:10] returns another vec with the relevant elements, for instance? I can pass an additional, but optional, end_idx into the function, for instance, but how would that work with a python slice object on python side?

sodiumnitrate avatar Sep 18 '23 15:09 sodiumnitrate

Is there an equivalent to make a class behave like a dictionary?

denniskb avatar Mar 01 '24 02:03 denniskb