Reputation: 1620
I am using boost.python to make two C++ classes available to Python,
class X {
public:
X();
}
class Y {
...
}
BOOST_PYTHON_MODULE(test) {
class_<X>("X", init<>());
class_<Y>("Y", init<>());
}
Whenever I create a new X in Python, I would like to run some code in C++ which creates a local object 'y' of type Y. So effectively, when I do
x = X()
in Python, I want this to run
y = Y()
as well, but from the X::X() constructor in C++.
I thought to use something like
scope().attr("y")=...
inside the X::X() constructor, but scope is always returning a NoneType object when called in this way (it works fine if I use this construction from within BOOST_PYTHON_MODULE, but that's not the right place for me).
Upvotes: 2
Views: 1070
Reputation: 51871
boost::python::scope
is more akin to namespaces rather than the scope of a code block. The Python/C API exposes a dictionary similar to locals()
through the PyEval_GetLocals()
function. It is possible to use this dictionary to inject variables into the current scope.
// Borrow a reference from the locals dictionary to create a handle.
// If PyEval_GetLocals() returns NULL, then Boost.Python will throw.
namespace python = boost::python;
python::object locals(python::borrowed(PyEval_GetLocals()));
// Inject an instance of Y into the frame's locals as variable 'y'.
// Boost.Python will handle the conversion of C++ Y to Python Y.
locals["y"] = Y();
Here is a complete example where an instance of example.Y
is injected into the caller's scope as variable y
when example.X
is constructed.
#include <boost/python.hpp>
/// @brief Mockup types.
struct X {};
struct Y {};
/// @brief Auxiliary function that will create X and inject an Y object
/// as 'y' into the caller's frame.
X* make_x_and_inject_y()
{
// Boost.Python objects may throw, so use a smart pointer that can
// release ownership to manage memory.
std::auto_ptr<X> x(new X());
// Borrow a reference from the locals dictionary to create a handle.
// If PyEval_GetLocals() returns NULL, then Boost.Python will throw.
namespace python = boost::python;
python::object locals(python::borrowed(PyEval_GetLocals()));
// Inject an instance of Y into the frame's locals as variable 'y'.
// Boost.Python will handle the conversion of C++ Y to Python Y.
locals["y"] = Y();
// Transfer ownership of X to Boost.Python.
return x.release();
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose X, explicitly suppressing Boost.Python from creating a
// default constructor, and instead exposing a custom constructor.
python::class_<X>("X", python::no_init)
.def("__init__", python::make_constructor(&make_x_and_inject_y))
;
python::class_<Y>("Y", python::init<>());
}
Interactive usage:
>>> import example
>>> def fun():
... assert('y' not in dir())
... example.X()
... assert('y' in dir()) # creating X injects y into scope
...
>>> assert('y' not in dir())
>>> fun()
>>> assert('y' not in dir())
>>> example.X()
<example.X object at 0xb746fa7c>
>>> assert('y' in dir()) # creating X injects y into scope
>>> assert(isinstance(y, example.Y))
In this implementation, I have opted to expose an auxiliary factory function to Python as X
's constructor rather than having X
's C++ constructor perform the injection of Y
. It is just a personal preference, but I often find that it provides a cleaner delimitation between the languages by limiting the amount of C++ types that are aware of Python.
Upvotes: 2