pybind/pybind11

Unmodified C++ object after calling a python callback

Opened this issue · 1 comments

Issue description

Hi,

I have a C++ code which calls a python function. This function modifies an object passed as its argument. When this object has been instantiated from python, modifications done by the python function are available to the c++ side.
However, an object, directly instantiated from the C++ side, is not modified after the function call.

The following example illustrates this behavior. With the function example_NOK, the object o1 is unmodified after the python function call. This is not the case with example_OK.

Is there a way to fix this issue and have my C++ object modified ?
Thanks for your help.

Reproducible example code

#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
#include <string>

namespace py = pybind11;

// custom type
class MyType {
public:
  MyType(int val) :val(val){}
  int val;
};

void example_OK(std::function<void(MyType&)> f, MyType& o){
  py::print("[example_OK] initial o = ", o);
  f(o);
  py::print("[example_OK] final o = ", o);
}

void example_NOK(std::function<void(MyType&)> f){
  MyType o1(10);
  py::print("[example_NOK] initial o = ", o1);
  f(o1);
  py::print("[example_NOK] final o = ", o1);
}

PYBIND11_MODULE(cmake_example, m) {
    py::class_<MyType>(m, "MyType")
      .def(py::init<int>())
      .def("__str__", [](MyType& o){return std::to_string(o.val);})
      .def_readwrite("val", &MyType::val)
    ;

    m.def("example_NOK", &example_NOK);
    m.def("example_OK", &example_OK);
}

python code:

import cmake_example as m

def f(o):
    o.val = -3

t = m.MyType(10)
m.example_OK(f, t)
m.example_NOK(f)

Ìt produces the following output

[example_OK] initial o =  10
[example_OK] final o =  -3
[example_NOK] initial o =  10
[example_NOK] final o =  10

In both case, we expected that the final value of the object was -3.

Do you know if the code works if you change your function signature to std::function<void (MyType*)>, and pass it a pointer?

I may have ran into a similar issue where pybind was copying the object rather than referencing it (but got a compile-time error because the type was non-copyable).
Here is where I encountered this, and the shim I put in place:
https://github.com/RobotLocomotion/drake/blob/0215e565aa38aaa9271d7757174a1c5ec61bcd6c/bindings/pydrake/systems/framework_py.cc#L102

If this is indeed the issue, then the stop gap would be to write either a specific shim (like what I did) or a general shim where you effectively want to specify py::return_value_policy::reference rather than py::return_value_policy::automatic (which the docs say is ...::copy for lvalue references). (I may try this out shortly, will let you know if I do.)

A longer-term solution might be to have some way to say that you want lvalue arguments to default to reference if a copy is not necessary.