returning list of class pointers that compare equal
I have a function returning a bp::list, so: static bp::list EXTget_params(Container *g) { bp::list param_list; int n_params = g->n_params; for (int i = 0; i < n_params; i++) { // Need this boost::ref to put a ref to the param into the list // rather than a param itself. (That would cause it to try to copy the // param, but it's listed as noncopyable so it would just get a TypeError.) param_list.append(bp::ref(g->params[i])); } return param_list; } Note that the Param is noncopyable. Its lifetime is managed by Container. The above works, except each time it runs it makes new bp::ref objects, which don't compare equal to each other: param1 = g.get_params()[0] param2 = g.get_params()[0] // should be same as param1 if param1 == param2: print "Success!" It doesn't print success, because the bp::ref objects are different, and don't have operator== to compare their referents (at least that's my guess about what's going on). Is there something else I can use instead of bp::ref here that will cause the list items to "point to" the same param each time I call get_params()? Thanks; -- Gary Oberbrunner
On Thu, Feb 6, 2014 at 1:53 PM, Gary Oberbrunner <garyo@genarts.com> wrote:
I have a function returning a bp::list, so:
static bp::list EXTget_params(Container *g) { bp::list param_list; int n_params = g->n_params; for (int i = 0; i < n_params; i++) { // Need this boost::ref to put a ref to the param into the list // rather than a param itself. (That would cause it to try to copy the // param, but it's listed as noncopyable so it would just get a TypeError.) param_list.append(bp::ref(g->params[i])); } return param_list; }
Note that the Param is noncopyable. Its lifetime is managed by Container. The above works, except each time it runs it makes new bp::ref objects, which don't compare equal to each other:
param1 = g.get_params()[0] param2 = g.get_params()[0] // should be same as param1 if param1 == param2: print "Success!"
It doesn't print success, because the bp::ref objects are different, and don't have operator== to compare their referents (at least that's my guess about what's going on). Is there something else I can use instead of bp::ref here that will cause the list items to "point to" the same param each time I call get_params()?
bp::ref isn't actually a kind of object; it's just a hint to Boost.Python that it shouldn't deep-copy your object when converting it to Python. The problem is simply that because you don't have a Python __eq__ operator defined, you're comparing the pointers of the Python objects that hold your C++ objects (mostly what you'd guessed). And when you create a new Python object from a C++ object in general, there's no easy way for Boost.Python to get at some previously-Python-ized instance of that same object so it can just return the same Python object again. I think there are two ways to address this: - You can wrap the things you're putting in the list in such a way that Boost.Python has them hold a pointer to their own Python selves. Then, when they're converted back to Python, I believe Boost.Python will just use that internal pointer. But this will only work if all the things in the list were originally constructed by calling their constructors in Python (otherwise those internal pointers can't get initialized), and I'm not positive it will all work even then. There's more information on how to do that here: http://www.boost.org/doc/libs/1_53_0/libs/python/doc/v2/class.html#HeldType - Add a __eq__/__ne__ overrides to the class of things you're putting in the list, with an implementation that compares C++ pointers, not Python pointers. This is almost certainly much easier, but it's a little less "complete" as a solution, in the sense that you'll still be getting different Python objects back for the same C++ object (so while "param2 == param1" will succeed, "param1 is param2" will still fail). Good luck! Jim
----- Original Message -----
From: "Jim Bosch" <talljimbo@gmail.com> ... - Add a __eq__/__ne__ overrides to the class of things you're putting in the list, with an implementation that compares C++ pointers, not Python pointers. This is almost certainly much easier, but it's a little less "complete" as a solution, in the sense that you'll still be getting different Python objects back for the same C++ object (so while "param2 == param1" will succeed, "param1 is param2" will still fail).
This is very helpful! That's OK about "is" not working, I think (though true, a little odd.) Can I override __hash__ too, so set membership works correctly? Also do I need to override __ne__; I think not? For the record, here's what I did. Defined a comparison function: static bool params_equal(Param &left, Param &right) { return &left == &right; } and then in the exposer: Param_exposer.def("__eq__", ¶ms_equal); -- Gary Oberbrunner
On Thu, Feb 6, 2014 at 2:43 PM, Gary Oberbrunner <garyo@genarts.com> wrote:
----- Original Message -----
From: "Jim Bosch" <talljimbo@gmail.com> ... - Add a __eq__/__ne__ overrides to the class of things you're putting in the list, with an implementation that compares C++ pointers, not Python pointers. This is almost certainly much easier, but it's a little less "complete" as a solution, in the sense that you'll still be getting different Python objects back for the same C++ object (so while "param2 == param1" will succeed, "param1 is param2" will still fail).
This is very helpful! That's OK about "is" not working, I think (though true, a little odd.)
Can I override __hash__ too, so set membership works correctly? Also do I need to override __ne__; I think not?
For the record, here's what I did. Defined a comparison function:
static bool params_equal(Param &left, Param &right) { return &left == &right; }
and then in the exposer:
Param_exposer.def("__eq__", ¶ms_equal);
That should be fine, as long as you don't care about what happens when you try to compare a Param to something else entirely (idiomatic Python behavior would be to return False; this implementation will likely raise an exception). You should be able to override __hash__ in much the same way, and I'd recommend checking to see if __ne__ works as expected before assuming you don't need to implement it yourself; I don't recall Python's behavior in this case, but I think it has a tendency not to define most operators implicitly. Jim
----- Original Message -----
From: "Jim Bosch" <talljimbo@gmail.com> To: "Development of Python/C++ integration" <cplusplus-sig@python.org> Sent: Thursday, February 6, 2014 2:51:59 PM Subject: Re: [C++-sig] returning list of class pointers that compare equal
On Thu, Feb 6, 2014 at 2:43 PM, Gary Oberbrunner <garyo@genarts.com> wrote:
----- Original Message -----
From: "Jim Bosch" <talljimbo@gmail.com> ... - Add a __eq__/__ne__ overrides to the class of things you're putting in the list, with an implementation that compares C++ pointers, not Python pointers. This is almost certainly much easier, but it's a little less "complete" as a solution, in the sense that you'll still be getting different Python objects back for the same C++ object (so while "param2 == param1" will succeed, "param1 is param2" will still fail).
This is very helpful! That's OK about "is" not working, I think (though true, a little odd.)
Can I override __hash__ too, so set membership works correctly? Also do I need to override __ne__; I think not?
For the record, here's what I did. Defined a comparison function:
static bool params_equal(Param &left, Param &right) { return &left == &right; }
and then in the exposer:
Param_exposer.def("__eq__", ¶ms_equal);
That should be fine, as long as you don't care about what happens when you try to compare a Param to something else entirely (idiomatic Python behavior would be to return False; this implementation will likely raise an exception).
I thought about that; for some reason the above works (no exception). It never calls my params_equal code; it must have some kind of fallback.
You should be able to override __hash__ in much the same way, and I'd recommend checking to see if __ne__ works as expected before assuming you don't need to implement it yourself; I don't recall Python's behavior in this case, but I think it has a tendency not to define most operators implicitly.
Just tested it, and as you suspect, indeed I do need __ne__, otherwise param1 != param2 returns True when it should return False. -- Gary Oberbrunner
participants (2)
-
Gary Oberbrunner -
Jim Bosch