[Boost Python] Cannot call base method from within python extended class method
Hello, My problem is the following: I am trying to bind a third party C++ library to python using boost-python. This library has a class Element and a class Owner (the later takes ownership of an Element object). To make things interesting, Element defines a pure virtual method "get_float". The Element should be sub-classed in python. Here is a simplified version of the library: // m1.hpp --------------------------------------------------------------------- // m1.hpp class Element { public: int get_int() { return -1; } virtual float get_float() = 0; }; class Owner { Element *m_element; public: Owner(): m_element(NULL) {} ~Owner() { delete m_element; } void set_element(Element* e) { delete m_element; m_element = e; }; Element* get_element() { return m_element; } }; and here is how I am binding to python: // m1_py.cpp ---------------------------------------------------------------- class ElementWrap : public Element, public wrapper<Element> { public: PyObject* self; ElementWrap(PyObject* self_): Element(), self(self_) { Py_INCREF(self); } ~ElementWrap() { Py_DECREF(self); } virtual float get_float() { return call_method<float>(self, "get_float"); } }; void set_element(Owner& owner, auto_ptr<Element> e) { owner.set_element(e.get()); e.release(); } BOOST_PYTHON_MODULE(m1) { class_<Element, auto_ptr<ElementWrap>, boost::noncopyable>("Element", init<>()) .def("get_int", &Element::get_int) .def("get_float", &Element::get_float) ; register_ptr_to_python<auto_ptr<Element> >(); implicitly_convertible<auto_ptr<ElementWrap>, auto_ptr<Element> >(); class_<Owner>("Owner", init<>()) .def("set_element", &set_element) .def("get_element", &Owner::get_element, return_internal_reference<>()) ; } ... but when I use it like this: # m2.py ----------------------------------------------------------------------- import m1 class SubElement(m1.Element): def get_float(self): return 4.5678 + self.get_int() element = SubElement() owner = m1.Owner() owner.set_element(element) element = owner.get_element() # ops! print (element.get_float()) I get an exception in the last line: Traceback (most recent call last): File "m2.py", line 16, in <module> print (element.get_float()) File "m2.py", line 8, in get_float return 1.0 + self.get_int() Boost.Python.ArgumentError: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue}) It seems that when I passed ownership to python I lost the ability to call a method from the base class. Can anyone help me? Thanks in advance Cheers Tiago
Hi Tiago, For ElementWrap::get_float, try: float get_float() { return this->get_override("get_float")(); } Binding to Python of that function should then be: .def("get_float", pure_virtual(&Element::get_float)) See also: http://www.boost.org/doc/libs/1_57_0/libs/python/doc/tutorial/doc/html/pytho... With regards, Paul P.S. Unrelated to your question: Try to use boost::shared_ptr instead of auto_ptr (it's deprecated). Regrettably, you can't use std::shared_ptr, since Boost Python doesn't support it. On 08/01/15 18:42, Tiago Coutinho wrote:
Hello,
My problem is the following:
I am trying to bind a third party C++ library to python using boost-python. This library has a class Element and a class Owner (the later takes ownership of an Element object).
To make things interesting, Element defines a pure virtual method "get_float". The Element should be sub-classed in python. Here is a simplified version of the library:
// m1.hpp --------------------------------------------------------------------- // m1.hpp
class Element { public: int get_int() { return -1; } virtual float get_float() = 0; };
class Owner { Element *m_element;
public:
Owner(): m_element(NULL) {} ~Owner() { delete m_element; }
void set_element(Element* e) { delete m_element; m_element = e; };
Element* get_element() { return m_element; } };
and here is how I am binding to python:
// m1_py.cpp ----------------------------------------------------------------
class ElementWrap : public Element, public wrapper<Element> { public: PyObject* self;
ElementWrap(PyObject* self_): Element(), self(self_) { Py_INCREF(self); }
~ElementWrap() { Py_DECREF(self); }
virtual float get_float() { return call_method<float>(self, "get_float"); }
};
void set_element(Owner& owner, auto_ptr<Element> e) { owner.set_element(e.get()); e.release(); }
BOOST_PYTHON_MODULE(m1) {
class_<Element, auto_ptr<ElementWrap>, boost::noncopyable>("Element", init<>()) .def("get_int", &Element::get_int) .def("get_float", &Element::get_float) ; register_ptr_to_python<auto_ptr<Element> >(); implicitly_convertible<auto_ptr<ElementWrap>, auto_ptr<Element> >();
class_<Owner>("Owner", init<>()) .def("set_element", &set_element) .def("get_element", &Owner::get_element, return_internal_reference<>()) ; }
... but when I use it like this:
# m2.py -----------------------------------------------------------------------
import m1
class SubElement(m1.Element):
def get_float(self): return 4.5678 + self.get_int()
element = SubElement() owner = m1.Owner() owner.set_element(element) element = owner.get_element()
# ops! print (element.get_float())
I get an exception in the last line: Traceback (most recent call last): File "m2.py", line 16, in <module> print (element.get_float()) File "m2.py", line 8, in get_float return 1.0 + self.get_int() Boost.Python.ArgumentError: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue})
It seems that when I passed ownership to python I lost the ability to call a method from the base class.
Can anyone help me?
Thanks in advance
Cheers Tiago
_______________________________________________ Cplusplus-sig mailing list Cplusplus-sig@python.org https://mail.python.org/mailman/listinfo/cplusplus-sig
Hi,
For ElementWrap::get_float, try: float get_float() { return this->get_override("get_float")(); }
Binding to Python of that function should then be: .def("get_float", pure_virtual(&Element::get_float))
See also: http://www.boost.org/doc/libs/1_57_0/libs/python/doc/tutorial/doc/ html/python/exposing.html#python.class_virtual_functions
With regards,
Paul
Doesn't solve this problem: $ cat m1_py.cpp // m1_py.cpp #include "boost/python.hpp" #include <memory> #include "m1.hpp" class ElementWrap : public Element, public boost::python::wrapper<Element> { public: // Element::get_float is pure virtual so no need for default implementation // if no python override found virtual float get_float() { boost::python::override py_override = this->get_override ("get_float"); if (!py_override) { // want a nice error message if no Python override of the // pure virtual method has been found boost::python::detail::pure_virtual_called(); } return py_override(); } }; void set_element(Owner& owner, std::auto_ptr<ElementWrap> e) { owner.set_element(e.get()); e.release(); } BOOST_PYTHON_MODULE(m1) { boost::python::class_<ElementWrap, std::auto_ptr<ElementWrap>, boost::noncopyable>( "Element", boost::python::init<>()) .def("get_int", &Element::get_int) .def("get_float", boost::python::pure_virtual(&Element::get_float)) ; boost::python::class_<Owner>("Owner", boost::python::init<>()) .def("set_element", &set_element) .def("get_element", &Owner::get_element, boost::python::return_internal_reference<>()) ; } 0 $ cat m2.py # m2.py import m1 class SubElement(m1.Element): def get_float(self): return 4.5678 + self.get_int() element = SubElement() print "element:", element owner = m1.Owner() print "element.get_int():", print element.get_int() print "owner.set_element(element)..." owner.set_element(element) print "element:", element try: print "element.get_int():", print element.get_int() except Exception as e: print "*** Exception caught:" print e print "element = owner.get_element(element)..." element = owner.get_element() print "element:", element # ops! print (element.get_float()) 0 $ PYTHONPATH=./bin/gcc-4.6.1/debug/ python2.7 m2.py element: <__main__.SubElement object at 0x7fef49783100> element.get_int(): -1 owner.set_element(element)... element: <__main__.SubElement object at 0x7fef49783100> element.get_int(): *** Exception caught: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue}) element = owner.get_element(element)... element: <__main__.SubElement object at 0x7fef49783100> Traceback (most recent call last): File "m2.py", line 29, in <module> print (element.get_float()) File "m2.py", line 8, in get_float return 4.5678 + self.get_int() Boost.Python.ArgumentError: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue}) We don't even get to the code to invoke a Python override here. Note how after owner.set_element() the SubElement instance becomes "inoperational". I don't quite understand the boost.python inner workings but the call to void set_element(Owner& owner, std::auto_ptr<ElementWrap> e) transfers ownership to the local auto_ptr e, so the auto_ptr held from the Python side gets modified (released ownership, now pointing to null). Much the same result as with the original implementation (although I seem to get the same object id for the get_element() return value here but that might be coincidence). Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
Hi, On Tue, 2015-01-13 at 12:13 +0100, Holger Joukl wrote:
Hi,
For ElementWrap::get_float, try: float get_float() { return this->get_override("get_float")(); }
Binding to Python of that function should then be: .def("get_float", pure_virtual(&Element::get_float))
See also: http://www.boost.org/doc/libs/1_57_0/libs/python/doc/tutorial/doc/ html/python/exposing.html#python.class_virtual_functions
With regards,
Paul
Doesn't solve this problem:
I confirm what Holger said. Paul's reply doesn't solve the problem. In fact, if I change the ElementWrap::get_float to: virtual float get_float() { return this->get_override("get_float")(); } I get in python: Traceback (most recent call last): File "m2.py", line 18, in <module> print (element.get_float()) TypeError: 'NoneType' object is not callable Which is very odd. It seems that boost get_override is not able to find the overloaded python SubElement.get_float() method. For reference I am using a Debian 7 machine 64bits. Python 2.7.3 GCC 4.7.2 boost-python 1.49.0 Thanks for all your help. Kind regards, Tiago
$ cat m1_py.cpp // m1_py.cpp #include "boost/python.hpp" #include <memory> #include "m1.hpp"
class ElementWrap : public Element, public boost::python::wrapper<Element> { public: // Element::get_float is pure virtual so no need for default implementation // if no python override found virtual float get_float() { boost::python::override py_override = this->get_override ("get_float"); if (!py_override) { // want a nice error message if no Python override of the // pure virtual method has been found boost::python::detail::pure_virtual_called(); } return py_override(); } };
void set_element(Owner& owner, std::auto_ptr<ElementWrap> e) { owner.set_element(e.get()); e.release(); }
BOOST_PYTHON_MODULE(m1) {
boost::python::class_<ElementWrap, std::auto_ptr<ElementWrap>, boost::noncopyable>( "Element", boost::python::init<>()) .def("get_int", &Element::get_int) .def("get_float", boost::python::pure_virtual(&Element::get_float)) ;
boost::python::class_<Owner>("Owner", boost::python::init<>()) .def("set_element", &set_element) .def("get_element", &Owner::get_element, boost::python::return_internal_reference<>()) ; } 0 $ cat m2.py # m2.py
import m1
class SubElement(m1.Element):
def get_float(self): return 4.5678 + self.get_int()
element = SubElement() print "element:", element owner = m1.Owner() print "element.get_int():", print element.get_int() print "owner.set_element(element)..." owner.set_element(element) print "element:", element try: print "element.get_int():", print element.get_int() except Exception as e: print "*** Exception caught:" print e print "element = owner.get_element(element)..." element = owner.get_element() print "element:", element
# ops! print (element.get_float())
0 $ PYTHONPATH=./bin/gcc-4.6.1/debug/ python2.7 m2.py element: <__main__.SubElement object at 0x7fef49783100> element.get_int(): -1 owner.set_element(element)... element: <__main__.SubElement object at 0x7fef49783100> element.get_int(): *** Exception caught: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue}) element = owner.get_element(element)... element: <__main__.SubElement object at 0x7fef49783100> Traceback (most recent call last): File "m2.py", line 29, in <module> print (element.get_float()) File "m2.py", line 8, in get_float return 4.5678 + self.get_int() Boost.Python.ArgumentError: Python argument types in Element.get_int(SubElement) did not match C++ signature: get_int(Element {lvalue})
We don't even get to the code to invoke a Python override here.
Note how after owner.set_element() the SubElement instance becomes "inoperational".
I don't quite understand the boost.python inner workings but the call to void set_element(Owner& owner, std::auto_ptr<ElementWrap> e) transfers ownership to the local auto_ptr e, so the auto_ptr held from the Python side gets modified (released ownership, now pointing to null).
Much the same result as with the original implementation (although I seem to get the same object id for the get_element() return value here but that might be coincidence).
Holger
Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
_______________________________________________ Cplusplus-sig mailing list Cplusplus-sig@python.org https://mail.python.org/mailman/listinfo/cplusplus-sig
For ElementWrap::get_float, try: float get_float() { return this->get_override("get_float")(); }
Binding to Python of that function should then be: .def("get_float", pure_virtual(&Element::get_float))
See also: http://www.boost.org/doc/libs/1_57_0/libs/python/doc/tutorial/doc/ html/python/exposing.html#python.class_virtual_functions
With regards,
Paul
Doesn't solve this problem:
I confirm what Holger said. Paul's reply doesn't solve the problem. In fact, if I change the ElementWrap::get_float to: virtual float get_float() { return this->get_override("get_float")(); }
I get in python: Traceback (most recent call last): File "m2.py", line 18, in <module> print (element.get_float()) TypeError: 'NoneType' object is not callable
Is your element object actually a SubElement instance or a m1.Element instance here? You should normally get this only when calling the get_float method of an m1.Element instance. This is basically a call to a pure virtual method which is really only possible because the original C++ element class is represented to Python by the derived ElementWrap class, which overrides the pure virtual to make callbacks from C++ to Python possible. I put the boost::python::detail::pure_virtual_called(); line into my example code to have a nicer error message for this case. No step closer to solving your actual problem, though... Holger Landesbank Baden-Wuerttemberg Anstalt des oeffentlichen Rechts Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz HRA 12704 Amtsgericht Stuttgart
participants (3)
-
Holger Joukl -
Paul Omta -
Tiago Coutinho