21.2 Basic class wrapping

As a very basic example of wrapping a class, let’s consider the SessionReader wrapper.


Listing 21.2: Basic class wrapping with Boost.Python
1void export_SessionReader() 
2
3    py::class_<SessionReader, 
4           std::shared_ptr<SessionReader>, 
5           boost::noncopyable>( 
6               "SessionReader", py::no_init) 
7 
8        .def("CreateInstance", SessionReader_CreateInstance) 
9        .staticmethod("CreateInstance") 
10 
11        .def("GetSessionName", &SessionReader::GetSessionName, 
12             py::return_value_policy<py::copy_const_reference>()) 
13 
14        .def("Finalise", &SessionReader::Finalise) 
15        ; 
16}

21.2.1 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:

We then have two arguments:

21.2.2 Wrapping member functions

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:

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.

Thin wrappers

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.


Listing 21.3: Thin wrapper example
1SessionReaderSharedPtr SessionReader_CreateInstance(py::list &ns) 
2
3    // ... some code here that converts a Python list to the standard 
4    // c/c++ (int argc, char **argv) format for command line arguments. 
5    // Then use this to construct a SessionReader and return it. 
6    SessionReaderSharedPtr sr = SessionReader::CreateInstance(argc, argv); 
7    return sr; 
8}

In Python, we can then simply call session = SessionReader.CreateInstance(sys.argv).

Dealing with references

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.

Dealing with 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:

More information on the memory management and how the memory is shared can be found in Section 23.

21.2.3 Inheritance

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:


Listing 21.4: Inheritance with Boost.Python
1void export_StdExpansion() 
2
3    py::class_<StdExpansion, 
4               std::shared_ptr<StdExpansion>, 
5               boost::noncopyable>( 
6                   "StdExpansion", py::no_init); 
7
8void export_StdQuadExp() 
9
10    py::class_<StdQuadExp, py::bases<StdExpansion>, 
11               std::shared_ptr<StdQuadExp> >( 
12                   "StdQuadExp", py::init<const LibUtilities::BasisKey&, 
13                   const LibUtilities::BasisKey&>()); 
14}

Note the following:

21.2.4 Wrapping enums

Most Nektar++ enumerators come in the form:

1enum MyEnum { 
2    eItemOne, 
3    eItemTwo, 
4    SIZE_MyEnum 
5}; 
6static const char *MyEnumMap[] = { 
7    "ItemOne" 
8    "ItemTwo" 
9    "ItemThree" 
10};

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.