Python Capsules

Python Capsules are useful for passing C pointers between different Python modules.  In particular, one can encapsulate a C function pointer in one module and unpack and call it in another module.

To begin, suppose we have two Boost.Python modules:

  1. Bird, which has methods setQuack() and callQuack().
  2. CppDuck, which has a method getQuack().

Our goal will be to get a function pointer with CppDuck.getQuack(), set it in another module in Bird.setQuack(), and finally call it with Bird.callQuack().

First let’s look at the code for CppDuck.cpp:

#include <boost/python.hpp>
#include <iostream>

using namespace boost::python;

void quack() {
  std::cout << "C++ Quack" << std::endl;
}

boost::python::object getQuack() {
  PyObject *result = PyCapsule_New((void*)&quack, "quacker", NULL);
  return object(boost::python::detail::new_reference(result));
}

BOOST_PYTHON_MODULE(CppDuck) {
  def("getQuack", &getQuack);
}

Note that we define a void (void) function “quack” which we will eventually pass to the Bird module. The method “getQuack” encapsulates it, and returns it as a Boost.Python object. Since PyCapsule_New() returns a new reference, we use boost::python::detail::new_reference to get the reference counting correct.

After compiling the module, we can try it out:

>>> import CppDuck
>>> CppDuck.getQuack()
<capsule object "quacker" at 0x100464930>

Let’s move on and see now how we can unpack the function pointer and call it in Bird.cpp:

#include <boost/python.hpp>
#include <iostream>

using namespace boost::python;

typedef void (quacker_t)(void);
quacker_t *quacker;

void default_quacker() {
  std::cout << "No noise came out." << std::endl;
}

void setQuack(object obj) {
  quacker = reinterpret_cast<quacker_t*>(PyCapsule_GetPointer(obj.ptr(), "quacker"));
  if (PyErr_Occurred())
    throw_error_already_set();
}

BOOST_PYTHON_MODULE(Bird) {
  quacker = &default_quacker;

  def("setQuack", &setQuack);
  def("callQuack", quacker);
}

Note the following conveniences in the code:

  1. We create a typedef the function type — in this case void (void) — since we use it in multiple places.
  2. We set a default quack function when the Python module is initialized. Without this, the Python interpreter will crash if we call callQuack before setting a valid quack.
  3. PyCapsule_GetPointer gets passed the same string (i.e., “quacker”) as we passed to PyCapsule_New in Bird.cpp.
  4. We use boost::python::throw_error_already_set() to display error messages. For example
    >>> Bird.setQuack(map)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: PyCapsule_GetPointer called with invalid PyCapsule object

With both modules compiled, we can try it:

>>> import Bird
>>> import CppDuck
>>>
>>> Bird.callQuack()
No noise came out.
>>>
>>> quack = CppDuck.getQuack()
>>> Bird.setQuack(quack)
>>>
>>> Bird.callQuack()
No noise came out.

2 thoughts on “Python Capsules

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s