As a very basic example of wrapping a class, let’s consider the SessionReader wrapper.
py::class_<>This Boost.Python object defines a Python class in C++. It is templated, and in this case we
have the following template arguments:
SessionReader is the class that will be wrapped
std::shared_ptr<SessionReader> indicates that this object should be stored
inside a shared (or smart) pointer, which we frequently use throughout the library,
as can be seen by the frequent use of SessionReaderSharedPtr
boost::noncopyable indicates that Boost.Python shouldn’t try to automatically
wrap the copy constructor of SessionReader. We add this here because of compiler
errors due to subclasses used inside SessionReader, but generally, this should be
used for abstract classes which can’t be copied.We then have two arguments:
"SessionReader" is the name of the class in Python.
py::no_init indicates this object has no publically-accessible initialiser. This
is because for SessionReader, we define a factory-type function called
CreateInstance instead.
We then call the .def function on the class_<>, which allows us to define member functions
on our class. This is equivalent to def-ing a function in Python. .def has two required
parameters, and one optional parameter:
"GetSessionName"
Boost.Python is very smart and can convert many Python objects to their equivalent C++
function arguments, and C++ return types of the function to their respective Python object.
Many times therefore, one only needs to define the .def() call.
However, there are some instances where we need to do some additional conversion, mask some C++ complexity from the Python interface, or deal with functions that return references. We describe ways to deal with this below.
Instead of defining a function pointer to a member of the C++ class, we can define a function pointer to a separate function that defines some extra functionality. This is called a thin wrapper.
As an example, consider the CreateInstance function. In C++ we pass this function the
command line arguments in the usual argc, argv format. In Python, command line
arguments are defined as a list of strings inside sys.argv. However, Boost.Python
does not know how to convert this list to argc, argv, so we need some additional
code.
In Python, we can then simply call session = SessionReader.CreateInstance(sys.argv).
When dealing with functions in C++ that return references, e.g. const NekDouble
&GetFactor() we need to supply an additional argument to .def(), since Python immutable
types such as strings and integers cannot be passed by reference. For a full list of
options, consult the Boost.Python guide. However a good rule of thumb is to use
copy_const_reference as highlighted above, which will create a copy of the const reference
and return this.
Array<OneD, >The LibUtilities/Python/BasicUtils/SharedArray.cpp file contains a number of
functions that allow for the automatic conversion of Nektar++ Array<OneD, > storage to and
from NumPy ndarray objects. This means that you can wrap functions that take these
as parameters and return arrays very easily. However bear in mind the following
caveats:
ndarray created from an Array<OneD, > (and vice versa) will share
their memory. Although this avoids expensive memory copies, it means that
changing the C++ array changes the contents of the NumPy array (and vice versa).
Use thin wrappers to overcome this problem. For examples of how to do this,
particularly in returning tuples, consult the StdRegions/StdExpansion.cpp wrapper
which contains numerous examples.
TwoD and ThreeD arrays are not supported.More information on the memory management and how the memory is shared can be found in Section 23.
Nektar++ makes heavy use of inheritance, which can be translated to Python quite easily
using Boost.Python. For a good example of how to do this, you can examine the StdRegions
wrapper for StdExpansion and its elements such as StdQuadExp. In a cut-down form, these
look like the following:
Note the following:
StdExpansion is an abstract class, so it has no initialiser and is non-copyable.
py::bases<StdExpansion> in the definition of StdQuadExp to define
its parent class. This does not necessarily need to include the full hierarchy of
C++ inheritance: in StdRegions the inheritance graph for StdQuadExp looks like
StdExpansion -> StdExpansion2D -> StdQuadExp. In the above wrapper, we
omit the StdExpansion2D call entirely.
py::init<> is used to show how to wrap a C++ constructor. This can accept any
arguments for which you have either written explicit wrappers or Boost.Python
already knows how to convert.
Most Nektar++ enumerators come in the form:
To wrap this, you can use the NEKPY_WRAP_ENUM macro defined in NekPyConfig.hpp, which in
this case can be used as NEKPY_WRAP_ENUM(MyEnum, MyEnumMap). Note that if instead
of const char * the map is defined as a const std::string, you can use the
NEKPY_WRAP_ENUM_STRING macro.