How to write pybindings for your project¶
Create a
private/pybindings
directory in your project.Write functions to instantiate Python proxies for your classes using boost::python.
Write a module declaration that gathers your classes together in a common module.
Write a
CMakeLists.txt
for yourprivate/pybindings
directory.Add the pybindings directory to your top-level
CMakeLists.txt
withadd_subdirectory('private/pybindings')
Registering your class with boost::python¶
Classes are registered by instantiating boost::python::class_<YourClass>.
using namespace boost::python;
scope particle_scope =
class_<I3Particle, bases<I3FrameObject>, boost::shared_ptr<I3Particle> >("I3Particle")
.def("GetTime", &I3Particle::GetTime)
.def("GetX", &I3Particle::GetX)
.def("GetY", &I3Particle::GetY)
.def("GetZ", &I3Particle::GetZ)
;
Each of these methods returns the scope object, so they can be chained together.
Wrapping containers¶
Vectors of your types can by exposed with:
class_<I3Vector<I3MyClass > >("I3MyClassSeries")
.def(vector_indexing_suite<I3Vector<I3MyClass > >())
;
This will, among other things, make indexing and iteration work as expected for a Python object.
Maps can be exposed with:
class_<I3Map<OMKey,I3Vector<I3MyClass > > >("I3MyClassSeriesMap")
.def(std_map_indexing_suite<I3Map<OMKey,I3Vector<I3MyClass > > >())
;
In addition to indexing, this will make the wrapped map behave as much like a Python dictionary as possible.
Wrapping enumerated types¶
Once you have exposed the methods of your class, you can expose enumerated types via boost::python::enum_<I3MyClass::MyEnumType>
.
enum_<I3Particle::FitStatus>("FitStatus")
.value("NotSet",I3Particle::NotSet)
.value("OK",I3Particle::OK)
.value("GeneralFailure",I3Particle::GeneralFailure)
.value("InsufficientHits",I3Particle::InsufficientHits)
.value("FailedToConverge",I3Particle::FailedToConverge)
.value("MissingSeed",I3Particle::MissingSeed)
.value("InsufficientQuality",I3Particle::InsufficientQuality)
;
Declaring the module¶
The wrapping code for each class should be placed in its own function:
#include <dataclasses/physics/I3Particle.h>
void register_I3Particle()
{
{
boost::python::class_<I3Particle,
bases<I3FrameObject>,
boost::shared_ptr<I3Particle > >("I3Particle")
...
}
}
The Python module for the project is declared with the macro I3_PYTHON_MODULE
. After loading your project’s library, you can call the registration functions you defined for each class:
I3_PYTHON_MODULE(dataclasses)
{
load_project("dataclasses",false);
void register_I3Particle();
void register_I3Position();
void register_I3RecoPulse();
}
If you named all your registration functions register_*
, you can use preprocessor macros to save some typing:
#define REGISTER_THESE_THINGS (I3Particle)(I3Position)(I3RecoPulse)
I3_PYTHON_MODULE(dataclasses)
{
load_project("dataclasses",false);
BOOST_PP_SEQ_FOR_EACH(I3_REGISTER, ~, REGISTER_THESE_THINGS);
}
Helpful preprocessor macros¶
Writing pybindings can involve plenty of boilerplate code. Luckily, we include some macros that can be used with Boost preprocessor sequences to reduce the tedium.
Boost preprocessor sequences¶
The header <boost/preprocessor/seq.hpp>
defines macros that can manipulate sequences. A sequence is a series of parenthesized tokens:
#define MY_SEQUENCE (a)(whole)(bunch)(of)(tokens)
These tokens can be expanded with
-
BOOST_PP_SEQ_FOR_EACH(Macro, Data, Seq)¶
Expand a sequence in place.
- Parameters:
Macro – A macro that takes three parameters: the head of the sequence, auxiliary data in Data, and an element of Seq.
Data – Arbitrary data to be passed to every call of Macro.
Seq – A sequence of tokens. Each of these tokens will be passed to Macro.
Most of the macros mentioned here can be used with
BOOST_PP_SEQ_FOR_EACH
to automate repetitive declarations.
The following macros are defined in cmake/I3.h.in
:
Wrapping methods verbatim¶
-
WRAP_DEF(R, Class, Fn)
Method-wrapping macro suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent class of the member function
Fn – Name of the member function
This macro can be used to expose your interface to Python exactly as it is in C++:
#define METHODS_TO_WRAP (GetTime)(GetX)(GetY)(GetZ) BOOST_PP_SEQ_FOR_EACH(WRAP_DEF, I3Particle, METHODS_TO_WRAP)
Since the Get/Set pattern is fairly common, there are iterable macros specifically for Get/Set. With these, one sequence can be used to define C++-style Get/Set methods and Python-style properties (see WRAP_PROP
).
-
WRAP_GET(R, Class, Name)
Define GetName(). Suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – The parent C++ class.
Name – The base name of the Get method.
-
WRAP_GETSET(R, Class, Name)
Define GetName() and SetName(). Suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – The parent C++ class.
Name – The base name of the Get/Set methods.
Example:
#define NAMES_TO_WRAP (Time)(X)(Y)(Z) BOOST_PP_SEQ_FOR_EACH(WRAP_GETSET, I3Particle, NAMES_TO_WRAP) BOOST_PP_SEQ_FOR_EACH(WRAP_PROP, I3Particle, NAMES_TO_WRAP)
There are also versions of these macros (WRAP_GET_INTERNAL_REFERENCE
and WRAP_GETSET_INTERNAL_REFERENCE
) that return a reference rather than a copy.
Exposing private member data via Get/Set¶
If you want to be nice to your users, you can wrap your Get/Set methods in Python properties:
-
PROPERTY(Class, Prop, Fn)
Add
Class.Prop
as a property with getter/setter functionsGetFn()
/SetFn()
- Parameters:
Class – Parent C++ class
Prop – The name of the Python property
Fn – The base name of the C++ Get/Set functions
-
PROPERTY_TYPE(Class, Prop, GotType, Fn)
Add
Class.Prop
as a property with getter/setter functionsGetFn()
/SetFn()
, specifying thatGetFn()
returnsGotType
. This is useful when wrapping overloaded getter functions.- Parameters:
Class – Parent C++ class
Prop – The name of the Python property
GotType – The type returned by GetFn()
Fn – The base name of the C++ Get/Set functions
-
WRAP_PROP(R, Class, Fn)
Add
Class.fn
as a property with getter/setter functionsGetFn()
/SetFn()
. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Fn – The name of the Python property and base name of the Get/Set functions
-
WRAP_PROP_RO(R, Class, Fn)
Add
Class.fn
as a property with getter functionGetFn()
. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Fn – The name of the Python property and base name of the Get function
Example:
#define DATA_TO_WRAP (Time)(X)(Y)(Z) BOOST_PP_SEQ_FOR_EACH(WRAP_PROP, I3Particle, DATA_TO_WRAP)
Now in Python, I3Particle.x (yes, lowercase) will call and return I3Particle::GetX() and I3Particle.x = 0 will call I3Particle::SetX(0).
For finer-grained control of the Python property name, use the trinary form:
PROPERTY(I3Particle, partyTime, Time)
Exposing public member data with access restrictions¶
You can expose public member data as properties, either read/write or read-only:
-
WRAP_RW(R, Class, Member)
Expose Member as a read/write Python property. Suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Member – Name of public data member and Python property
-
WRAP_RO(R, Class, Member)
Expose Member as a read-only Python property. Suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Member – Name of public data member and Python property
Example:
#define MEMBERS_TO_WRAP (value)(some_other_value) BOOST_PP_SEQ_FOR_EACH(WRAP_RO, I3MyClass, MEMBERS_TO_WRAP)
Wrapping methods with call policies¶
If you need finer-grained control of the return type of your wrapped methods, you can use the following macros:
-
GETSET(Objtype, GotType, Name)
Define getter/setter methods to return by value.
- Parameters:
Objtype – The parent C++ class.
GotType – The type of object returned by Get()
Name – The base name of the Get/Set methods.
For a name
X
, this will defineObjtype::GetX()
to return aGotType
by value. This is appropriate for POD like ints and doubles. It will also defineSetX()
.
-
GETSET_INTERNAL_REFERENCE(Objtype, GotType, Name)
Define getter/setter methods to return by reference.
- Parameters:
Objtype – The parent C++ class.
GotType – The type of object returned by Get()
Name – The base name of the Get/Set methods.
This will define
Objtype::GetX()
to return a reference toGotType
, whereGotType
is still owned by the parent object. This is appropriate for compound objects like vectors and maps.
There are also trinary versions of these macros for use with BOOST_PP_SEQ_FOR_EACH
:
-
WRAP_GET_INTERNAL_REFERENCE(R, Class, Name)
Define
GetName()
to return an internal reference. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – The parent C++ class.
Name – The base name of the Get method.
-
WRAP_GETSET_INTERNAL_REFERENCE(R, Class, Name)
Define
GetName()
andSetName()
.GetName()
will return an internal reference. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – The parent C++ class.
Name – The base name of the Get/Set methods.
-
WRAP_PROP_RO_INTERNAL_REFERENCE(R, Class, Fn)
Add
Class.fn
as a property with getter functionGetFn()
.GetFn()
will return a reference to the object owned by the C++ instance. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Fn – The name of the Python property and base name of the Get function
-
WRAP_PROP_INTERNAL_REFERENCE(R, Class, Fn)
Add
Class.fn
as a property with getter/setter functionsGetFn()
/SetFn()
.GetFn()
will return a reference to the object owned by the C++ instance. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – Parent C++ class
Fn – The name of the Python property and base name of the Get/Set functions
Wrapping enumerated types¶
-
WRAP_ENUM_VALUE(R, Class, Name)
Add the value Name to an enumerated type. Suitable for use with
BOOST_PP_SEQ_FOR_EACH
.- Parameters:
Class – The parent C++ class.
Name – The name of the C++ value.
Example:
enum_<I3Particle::FitStatus>("FitStatus") #define FIT_STATUS (NotSet)(OK)(GeneralFailure)(InsufficientHits) \ (FailedToConverge)(MissingSpeed)(InsufficientQuality) BOOST_PP_SEQ_FOR_EACH(WRAP_ENUM_VALUE, I3Particle, FIT_STATUS) ;
Constructing a Python module¶
-
I3_REGISTER(r, data, t)
For name
Name
, callvoid register_Name()
. Suitable for use withBOOST_PP_SEQ_FOR_EACH
.- Parameters:
t – The suffix of the function name, e.g. register_t().
data – unused.
-
I3_PYTHON_MODULE(module_name)
Declare the following code to be run when the module is initialized.
- Parameters:
module_name – The name of the Python module. Must be a legal Python variable name.
Example:
#define REGISTER_THESE_THINGS (I3Particle)(I3Position)(I3RecoPulse) I3_PYTHON_MODULE(dataclasses) { load_project("dataclasses",false); BOOST_PP_SEQ_FOR_EACH(I3_REGISTER, ~, REGISTER_THESE_THINGS); }
Gotchas¶
<unresolved overloaded function type> errors¶
You may be mystified by errors like these:
error: No match for 'boost::python::class_<
I3MCPMTResponse, boost::shared_ptr<I3MCPMTResponse>,
boost::python::detail::not_specified,
boost::python::detail::not_specified
>::def(const char [11], <unresolved overloaded function type>)'
This can happen when the wrapped class exposes two different versions of the function, for example returning a const or non-const type. In this case, you have to specify the return type by hand. The BOOST_PP_SEQ_FOR_EACH
tricks will not work; you’ll need to use GETSET
or PROPERTY_TYPE
to wrap each name individually instead.
Naming conventions¶
Python properties are preferred over C++-style Get/Set methods. The exposed Python module should conform our Python Coding Standards as closely as possible.