Python + Boost Python V2 + downcasting
Hello, I'm experiencing some embedding+extending with BPLv2 - and I could not find any answer to this simple question : I have, say, a c++ class 'Base' and a c++ class 'Derived': class Base { public: virtual int do_stuff() { return 0; } }; class Derived { public: virtual int do_more() { return 100; } }; class ObjectServer { public: Base* getObject(unsigned id) { return m_objects[id]; } Base* m_objects[12]; } declared in my module like this: class_<Base>("Base") .def("do_stuff", &Base::do_stuff); class_<Derived, bases<Base> >("Derived") .def("do_more", &Derived::do_more); class_<ObjectServer>("ObjectServer") .def("getObject", &ObjectServer::getObject, return_internal_reference<>()); everything works fine - except that I can't get python to see the _real_ class of the objects returned by getObject. I tried the following: ... obj = objectServer.getObject(0) print isinstance(obj, Base) print isinstance(obj, Derived) print issubclass(Derived, Base) print obj.__class__ and I get, when 'obj' is really a 'Derived*' ... 1 0 1 module.Base how can I get to know that an object is an instance of Derived ?! is there a builtin way in Python ? a BPL v2 way ? I even thought about implementing my own 'isinstance' function but I'm not familiar enough with boost python & python !!... Any help or advice will be _greatly_ appreciated :) Thanks in advance, Nicolas. PS. Please CC your response to n_lelong@hotmail.com as I'm not a regular contributor to this list!
"Nicolas Lelong" <n_lelong@hotmail.com> writes:
Hello,
I'm experiencing some embedding+extending with BPLv2 - and I could not find any answer to this simple question :
I have, say, a c++ class 'Base' and a c++ class 'Derived':
class Base { public: virtual int do_stuff() { return 0; } };
class Derived { public: virtual int do_more() { return 100; } };
class ObjectServer { public: Base* getObject(unsigned id) { return m_objects[id]; }
Base* m_objects[12]; }
declared in my module like this:
class_<Base>("Base") .def("do_stuff", &Base::do_stuff);
class_<Derived, bases<Base> >("Derived") .def("do_more", &Derived::do_more);
class_<ObjectServer>("ObjectServer") .def("getObject", &ObjectServer::getObject, return_internal_reference<>());
everything works fine - except that I can't get python to see the _real_ class of the objects returned by getObject. I tried the following:
... obj = objectServer.getObject(0) print isinstance(obj, Base) print isinstance(obj, Derived) print issubclass(Derived, Base) print obj.__class__
and I get, when 'obj' is really a 'Derived*' ... 1 0 1 module.Base
how can I get to know that an object is an instance of Derived ?!
Not easily. You could pass it to a function which takes a Derived& or a Derived*.
is there a builtin way in Python ? a BPL v2 way ?
Nope. It is holding a Base* internally, and using C++ runtime polymorphism to call the right methods.
I even thought about implementing my own 'isinstance' function but I'm not familiar enough with boost python & python !!... Any help or advice will be _greatly_ appreciated :)
Why do you need to do this? Such downcasting is usually the mark of a poor design. -- David Abrahams dave@boost-consulting.com * http://www.boost-consulting.com
I even thought about implementing my own 'isinstance' function but I'm not familiar enough with boost python & python !!... Any help or advice will be _greatly_ appreciated :)
Why do you need to do this? Such downcasting is usually the mark of a poor design.
Thanks for your reply, I'm aware that this design decision may not be the best - but it suits the needs I have for now... I have a tree structure representing hierarchical dependencies between elements of a graphical system. The objects have a similar base interface that is used to navigate through the graph - but each class of item has a set a specific properties. We decided to use a 'node class' identification system based on C++ RTTI because we did not want to reinvent the weel. Now, if I really can't get the correct info in Python, I'll have two choices: - propose a change in the node class identification system (i'd rather not) - drop Python (i'd rather not !). I believe that there must be a way to extract to RTTI info from the object holder as it is done to allow the call of a function that takes a Derived&(or *) in parameter - it would help me going on in my Python evaluation - before having to rethink the whole heart of the application ! Could you give me a few hints to create my own implementation of 'isinstance' as i don't have much time to investigate the boost python v2 internal structures ? Thank you, Nicolas.
"Nicolas Lelong" <n_lelong@hotmail.com> writes:
I even thought about implementing my own 'isinstance' function but I'm not familiar enough with boost python & python !!... Any help or advice will be _greatly_ appreciated :)
Why do you need to do this? Such downcasting is usually the mark of a poor design.
Thanks for your reply,
I'm aware that this design decision may not be the best - but it suits the needs I have for now... I have a tree structure representing hierarchical dependencies between elements of a graphical system. The objects have a similar base interface that is used to navigate through the graph - but each class of item has a set a specific properties.
We decided to use a 'node class' identification system based on C++ RTTI because we did not want to reinvent the weel.
OK, I understand.
Now, if I really can't get the correct info in Python, I'll have two choices: - propose a change in the node class identification system (i'd rather not) - drop Python (i'd rather not !).
I believe that there must be a way to extract to RTTI info from the object holder as it is done to allow the call of a function that takes a Derived&(or *) in parameter - it would help me going on in my Python evaluation - before having to rethink the whole heart of the application ! Could you give me a few hints to create my own implementation of 'isinstance' as i don't have much time to investigate the boost python v2 internal structures ?
Let's look for other solutions; I just don't think 'isinstance' is appropriate. You see, Python really is handling a class called 'Base', even when the pointer actually refers to a 'Derived', and Python's isinstance just crawls up the Python class hierarchy to find the class you're looking for. [digression: I notice that you're using return_internal_reference<>() to return the Base* from the ObjectServer. Does the ObjectServer actually control the lifetime of the objects, or is it simply holding pointers? I ask because I don't see a destructor, so in the code you sent, return_internal_reference<> is inappropriate (maybe you were just eliminating details for the sake of the post?) -- the point of return_internal_reference<> is to keep the owning object alive as long as the owned objects are also alive. If something else is managing their lifetimes, you may need another mechanism. ] Ultimately, I think the best solution is going to be to add a new kind of ReturnValuePolicy which uses typeid() on the pointer to get the most-derived class, looks up the corresponding Python class in the converter::registry, and builds a pointer_holder around it using the appropriate Python class type. Hmm, looking carefully, this might not require a new ReturnValuePolicy, and could be as simple as making some judicious changes to boost/python/object/make_instance.hpp.** This is just one of whole category of changes needed to support C++ runtime polymorphism really well. I'm currently trying to sort out my priorities at the moment; if I can secure funding for this work it will certainly get moved to the top of my queue. In the meantime, if the ObjectServer is really the owner of the C++ objects, you might consider storing boost::python::object in its m_objects array. If you convert your Derived objects to boost::python::object at their point of creation, the full dynamic type will be preserved for Python. -Dave **There are some open questions... for example, what happens if the most-derived C++ class hasn't been registered by the user with a class_<> declaration? It's possible to attempt to find a most-derived-registered class by using the facilities in libs/python/src/object/inheritance.cpp, but I'm not sure it's worth it, and that capability could be added later. -- David Abrahams dave@boost-consulting.com * http://www.boost-consulting.com
I wrote:
Let's look for other solutions; I just don't think 'isinstance' is appropriate. You see, Python really is handling a class called 'Base', even when the pointer actually refers to a 'Derived', and Python's isinstance just crawls up the Python class hierarchy to find the class you're looking for.
<snip>
Ultimately, I think the best solution is going to be to add a new kind of ReturnValuePolicy which uses typeid() on the pointer to get the most-derived class, looks up the corresponding Python class in the converter::registry, and builds a pointer_holder around it using the appropriate Python class type. Hmm, looking carefully, this might not require a new ReturnValuePolicy, and could be as simple as making some judicious changes to boost/python/object/make_instance.hpp.**
In case it wasn't clear, the above change, if I can figure out how to do it, would make isinstance() work as you expect. -Dave -- David Abrahams dave@boost-consulting.com * http://www.boost-consulting.com
participants (2)
-
David Abrahams -
Nicolas Lelong