Skip to content

Python Exposure

Matt King edited this page Oct 4, 2019 · 8 revisions

Now you have added all of the C++ code to CATAP for your new hardware object, it is time to define how this will be accessed by scientists and app developers via Python.

Below are some examples of the things that you will have to expose, and some of the optional extras that you can expose if you choose to do so.

CATAP will utilise the Boost.Python framework (a C++ library for python exposure) that will wrap the C++ classes/functions/variables you have defined into Python code, ready to be used by scientists and app developers.

Boost Python Exposure

When exposing your C++ to Python, there is some "Boiler-plate" code that will need to be included:

DEFINES

Includes

Below is a list of the headers that you should include when exposing your C++ hardware class to Python:

  • The header of your hardware class
  • <boost/python.hpp>
  • <boost/python/suite/indexing/map_indexing_suite.hpp>
  • <boost/python/suite/indexing/vector_indexing_suite.hpp>
  • <boost/python/enum.hpp>

Any other headers you choose to include should only be used explicitly within your exposure to reduce redundancies.

Necessary Classes to Expose

There are 3 classes that you will have to expose:

  • The hardware class you have created
  • The factory class for that hardware object
  • The EPICS Interface for that hardware object

Class Exposure

When exposing a class, for example a Magnet, you will write:

boost::python::class_<Magnet>("Magnet", boost::python::no_init);

The boost::python::class_ part tells Boost.Python that you want to expose this as a Python class.

The <Magnet> part tells Boost Python the Type of the class you want to expose so it can access methods/variables from that class etc.

The ("Magnet", boost::python::no_init) part tells Boost Python what you want your class to be referred to as in Python (in this case we want it to be called Magnet) and the boost::python::no_init tells Boost Python that this class does not need a list of arguments to be initialized.

By defining boost::python::no_init we are telling Python to use the class' default constructor (the one with no arguments) when creating the class object.

Function Exposure

One you have defined the class you want expose using the code above, you can begin to define the functions of that class you want to expose to Python.

To expose functions using the Boost.Python framework, underneath the class exposure we write lines of the form:

boost::python::class_<MagnetFactory>("MagnetFactory", boost::python::no_init)
    // defining an init function related to a non-default constructor that takes a boolean argument
    .def(boost::python::init<bool>())
    // defining the setup method for the factory that takes primitive argument, and return primitive argument
    // Python will know how to deal with primitive types automatically so no need for a return_value_policy
    .def("setup", &MagnetFactory::setup)
    // defining a function that returns a non-primitive type (Magnet)
    // since Python does not know how to deal with a return-type of Magnet 
    // we tell it to just reference the existing object that is created in C++
    .def("getMagnet", &MagnetFactory::getMagnet, boost::python::return_value_policy<boost::python::referece_existing_object>())

so to summarize:

  • expose a function using the .def method
  • give it the name you want it to be seen as in python i.e. "getMagnet"
  • if the return type is non-primitive then define a return_value_policy so Python knows how to return the type from that function

Variable Exposure

In C++ it is considered good practice to have getter and setter functions for your class member variables. However, a pythonic way of setting your member variables values is as such:

# C++ style of setting/getting member variable
current = magnet.getCurrent()
magnet.setCurrent(current+0.5)

# Pythonic style of setting/getting member variable
current = magnet.current
magnet.current = current + 0.5

Depending on your programming background, you will take a preference to one of these. CATAP should cater to both of these styles. So it is important to not only expose your getter/setter functions for the class, but to expose the member variables as well.

The following code gives examples of how to expose member variables to Python:

boost::python::class_<Magnet, boost::python::bases<Hardware>, boost::noncopyable>("Magnet", boost::python::no_init)
  // state the name you want to see via Python, define the getter, define the setter
  .add_property("current", &Magnet::getCurrent, &Magnet::setCurrent)
  // we don't want the user to set a manufacturer name, so define the variable as "readonly"
  // state the name you want to see via Python, define the getter for that variable
  .def_readonly("manufacturer", &Magnet::getManufacturer)

Optional Extra Exposure

Now we have looked at the 3 main types of things you will expose to Python (class, function, variable), you have the tools to exposure any optional extras from C++ to Python.

Clone this wiki locally