Reputation:
I've run into yet another snag in my Boost.Python adventure.
I have the following Python module defined:
#include <Python.h>
#include <iostream>
#include <boost/python.hpp>
using namespace boost;
using namespace boost::python;
struct Base { };
void foo(boost::shared_ptr<Base>)
{
std::cout << "yay\n";
}
BOOST_PYTHON_MODULE(Test)
{
class_<Base, shared_ptr<Base>, noncopyable>("Base", init<>());
def("foo", foo);
}
Running the following script:
from Test import *
class Bar(Base):
def __init__(self):
pass
foo(Base()) #works
foo(Bar()) #error
The last line yields an error to the effect of this:
Python argument types in
foo(Bar)
did not match C++ signature:
foo(class boost::shared_ptr<struct Base>)
Now my question is why doesn't this work? Surely the type system should be able to work out the Bar
is a Base
instance?
http://coliru.stacked-crooked.com/a/43f111fb3032a20a
Any help is appreciated!
Upvotes: 0
Views: 454
Reputation: 51881
In this particular case, the error message is misleading. The function receives an argument with the right type; however, the argument has an inappropriate value. The Bar
initializer is not initializing the Python Base
part of its hierarchy. The Python instance does not contain a boost::shared_ptr<Base>
instance, resulting in Boost.Python failing to dispatch to the C++ function:
class Bar(Base):
def __init__(self):
pass # Base is not initialized.
fun(Base()) # No boost::shared_ptr<Base> instance.
To resolve the issue, explicitly invoke Base.__init__()
within Bar.__init__()
:
class Bar(Base):
def __init__(self):
Base.__init__(self) # Instantiates boost::shared_ptr<Base>.
fun(Bar()) # Boost.Python will extract boost::shared_ptr<Base> from Bar().
For details, in Python if a derived class defines an __init__()
method, then it should explicitly invoke the parent class' __init__()
method. The Python documentation states:
If a base class has an
__init__()
method, the derived class’s__init__()
method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example:BaseClass.__init__(self, [args...])
.
In Boost.Python, C++ class wrappers have an instance_holder
. These objects hold C++ instances within their Python object wrapper, and instantiation of the C++ object occurs within the Python object's __init__
function:
When an
__init__
function for a wrapped C++ class is invoked, a newinstance_holder
instance is created and installed in the Python object [...]
Therefore, if the Python object's __init__()
method is not invoked, the internal C++ object will not be instantiated. When an exposed C++ function is invoked from Python, Boost.Python will examine the invocation arguments, attempting to identify a matching C++ function within a set of exposed functions. When no match is found, it will raise a Boost.Python.ArgumentError
exception, listing the argument types and the set of C++ functions for which it failed to match.
Here is a complete example demonstrating having two different Python types inherit from an exposed C++ type, where one hierarchy is initialized properly and the other is not:
#include <boost/python.hpp>
struct base {};
void foo(boost::shared_ptr<base>) {}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<base, boost::shared_ptr<base>, boost::noncopyable>(
"Base", python::init<>())
;
python::def("foo", &foo);
}
Interactive usage:
>>> import example
>>> class GoodDerived(example.Base):
... def __init__(self):
... example.Base.__init__(self)
...
>>> class BadDerived(example.Base):
... def __init__(self):
... pass
...
>>> assert(isinstance(GoodDerived(), example.Base))
>>> assert(isinstance(BadDerived(), example.Base))
>>> try:
... example.foo(GoodDerived())
... got_exception = False
... except:
... got_exception = True
... finally:
... assert(not got_exception)
...
>>> try:
... example.foo(BadDerived())
... got_exception = False
... except:
... got_exception = True
... finally:
... assert(got_exception)
Note that while the type hierarchy is correct and verifiable via isinstance(()
, the types do not indicate if an instance has an appropriate value.
Upvotes: 2